From c519cee5d1d84468acb9559a0615e8ec2e587b98 Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 15 May 2025 16:32:57 -0400 Subject: [PATCH 1/8] frontend --- .../20250515164622_select-org-products.ts | 21 +++ backend/src/db/schemas/organizations.ts | 6 +- backend/src/server/routes/v1/index.ts | 4 +- .../server/routes/v1/organization-router.ts | 9 +- backend/src/server/routes/v3/index.ts | 2 +- backend/src/services/org/org-schema.ts | 6 +- backend/src/services/org/org-service.ts | 12 +- backend/src/services/org/org-types.ts | 4 + .../src/hooks/api/organization/queries.tsx | 12 +- frontend/src/hooks/api/organization/types.ts | 8 ++ .../MinimizedOrgSidebar.tsx | 120 ++++++++++-------- .../OrgGeneralTab/OrgGeneralTab.tsx | 14 +- .../OrgProductSelectSection.tsx | 84 ++++++++++++ .../OrgProductSelectSection/index.tsx | 1 + 14 files changed, 232 insertions(+), 71 deletions(-) create mode 100644 backend/src/db/migrations/20250515164622_select-org-products.ts create mode 100644 frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx create mode 100644 frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/index.tsx diff --git a/backend/src/db/migrations/20250515164622_select-org-products.ts b/backend/src/db/migrations/20250515164622_select-org-products.ts new file mode 100644 index 000000000..f143d7c0e --- /dev/null +++ b/backend/src/db/migrations/20250515164622_select-org-products.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.boolean("secretsProductEnabled").defaultTo(true); + t.boolean("pkiProductEnabled").defaultTo(true); + t.boolean("kmsProductEnabled").defaultTo(true); + t.boolean("sshProductEnabled").defaultTo(true); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.dropColumn("secretsProductEnabled"); + t.dropColumn("pkiProductEnabled"); + t.dropColumn("kmsProductEnabled"); + t.dropColumn("sshProductEnabled"); + }); +} diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index 8d8279802..1b175f0c0 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -28,7 +28,11 @@ export const OrganizationsSchema = z.object({ privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(), privilegeUpgradeInitiatedAt: z.date().nullable().optional(), bypassOrgAuthEnabled: z.boolean().default(false), - userTokenExpiration: z.string().nullable().optional() + userTokenExpiration: z.string().nullable().optional(), + secretsProductEnabled: z.boolean().default(true).nullable().optional(), + pkiProductEnabled: z.boolean().default(true).nullable().optional(), + kmsProductEnabled: z.boolean().default(true).nullable().optional(), + sshProductEnabled: z.boolean().default(true).nullable().optional() }); export type TOrganizations = z.infer; diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 018e457fa..6fa6e4d54 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -110,7 +110,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" }); await pkiRouter.register(registerPkiSubscriberRouter, { prefix: "/subscribers" }); }, - { prefix: "/pki" } + { prefix: "/pki" } // TODO(andrey): Block this if PKI PRODUCT disabled? ); await server.register(registerProjectBotRouter, { prefix: "/bot" }); @@ -129,7 +129,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerUserEngagementRouter, { prefix: "/user-engagement" }); await server.register(registerDashboardRouter, { prefix: "/dashboard" }); - await server.register(registerCmekRouter, { prefix: "/kms" }); + await server.register(registerCmekRouter, { prefix: "/kms" }); // TODO(andrey): Block this if KMS PRODUCT disabled? await server.register(registerExternalGroupOrgRoleMappingRouter, { prefix: "/external-group-mappings" }); await server.register( diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index da1a251ff..991387d15 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -275,7 +275,11 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, { message: "Duration value must be at least 1" } ) - .optional() + .optional(), + secretsProductEnabled: z.boolean().optional(), + pkiProductEnabled: z.boolean().optional(), + kmsProductEnabled: z.boolean().optional(), + sshProductEnabled: z.boolean().optional() }), response: { 200: z.object({ @@ -286,6 +290,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { + console.log("REQ BODY"); + console.log(req.body); + const organization = await server.services.org.updateOrg({ actor: req.permission.type, actorId: req.permission.id, diff --git a/backend/src/server/routes/v3/index.ts b/backend/src/server/routes/v3/index.ts index ed8401560..b0656f1f1 100644 --- a/backend/src/server/routes/v3/index.ts +++ b/backend/src/server/routes/v3/index.ts @@ -9,7 +9,7 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerSignupRouter, { prefix: "/signup" }); await server.register(registerLoginRouter, { prefix: "/auth" }); await server.register(registerUserRouter, { prefix: "/users" }); - await server.register(registerSecretRouter, { prefix: "/secrets" }); + await server.register(registerSecretRouter, { prefix: "/secrets" }); // TODO(andrey): Block this if SECRETS PRODUCT disabled? await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" }); await server.register(registerExternalMigrationRouter, { prefix: "/migrate" }); }; diff --git a/backend/src/services/org/org-schema.ts b/backend/src/services/org/org-schema.ts index 5a1a4c333..df5b86d5c 100644 --- a/backend/src/services/org/org-schema.ts +++ b/backend/src/services/org/org-schema.ts @@ -18,5 +18,9 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({ privilegeUpgradeInitiatedByUsername: true, privilegeUpgradeInitiatedAt: true, bypassOrgAuthEnabled: true, - userTokenExpiration: true + userTokenExpiration: true, + secretsProductEnabled: true, + pkiProductEnabled: true, + kmsProductEnabled: true, + sshProductEnabled: true }); diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index d794391c1..7fb244c36 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -355,7 +355,11 @@ export const orgServiceFactory = ({ selectedMfaMethod, allowSecretSharingOutsideOrganization, bypassOrgAuthEnabled, - userTokenExpiration + userTokenExpiration, + secretsProductEnabled, + pkiProductEnabled, + kmsProductEnabled, + sshProductEnabled } }: TUpdateOrgDTO) => { const appCfg = getConfig(); @@ -457,7 +461,11 @@ export const orgServiceFactory = ({ selectedMfaMethod, allowSecretSharingOutsideOrganization, bypassOrgAuthEnabled, - userTokenExpiration + userTokenExpiration, + secretsProductEnabled, + pkiProductEnabled, + kmsProductEnabled, + sshProductEnabled }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); return org; diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index 702cd25bf..ea336d146 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -75,6 +75,10 @@ export type TUpdateOrgDTO = { allowSecretSharingOutsideOrganization: boolean; bypassOrgAuthEnabled: boolean; userTokenExpiration: string; + secretsProductEnabled: boolean; + pkiProductEnabled: boolean; + kmsProductEnabled: boolean; + sshProductEnabled: boolean; }>; } & TOrgPermission; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 06125b1d7..737cc6bd3 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -112,7 +112,11 @@ export const useUpdateOrg = () => { selectedMfaMethod, allowSecretSharingOutsideOrganization, bypassOrgAuthEnabled, - userTokenExpiration + userTokenExpiration, + secretsProductEnabled, + pkiProductEnabled, + kmsProductEnabled, + sshProductEnabled }) => { return apiRequest.patch(`/api/v1/organization/${orgId}`, { name, @@ -124,7 +128,11 @@ export const useUpdateOrg = () => { selectedMfaMethod, allowSecretSharingOutsideOrganization, bypassOrgAuthEnabled, - userTokenExpiration + userTokenExpiration, + secretsProductEnabled, + pkiProductEnabled, + kmsProductEnabled, + sshProductEnabled }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 6f63d003e..5da571504 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -20,6 +20,10 @@ export type Organization = { allowSecretSharingOutsideOrganization?: boolean; userTokenExpiration?: string; userRole: string; + secretsProductEnabled: boolean; + pkiProductEnabled: boolean; + kmsProductEnabled: boolean; + sshProductEnabled: boolean; }; export type UpdateOrgDTO = { @@ -34,6 +38,10 @@ export type UpdateOrgDTO = { allowSecretSharingOutsideOrganization?: boolean; bypassOrgAuthEnabled?: boolean; userTokenExpiration?: string; + secretsProductEnabled?: boolean; + pkiProductEnabled?: boolean; + kmsProductEnabled?: boolean; + sshProductEnabled?: boolean; }; export type BillingDetails = { diff --git a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx b/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx index b85499073..b466c33d5 100644 --- a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx @@ -268,62 +268,70 @@ export const MinimizedOrgSidebar = () => {
- - {({ isActive }) => ( - - Secrets - - )} - - - {({ isActive }) => ( - - PKI - - )} - - - {({ isActive }) => ( - - KMS - - )} - - - {({ isActive }) => ( - - SSH - - )} - + {currentOrg.secretsProductEnabled && ( + + {({ isActive }) => ( + + Secrets + + )} + + )} + {currentOrg.pkiProductEnabled && ( + + {({ isActive }) => ( + + PKI + + )} + + )} + {currentOrg.kmsProductEnabled && ( + + {({ isActive }) => ( + + KMS + + )} + + )} + {currentOrg.sshProductEnabled && ( + + {({ isActive }) => ( + + SSH + + )} + + )}
{({ isActive }) => ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx index 917d08be9..85fdfa132 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx @@ -3,14 +3,18 @@ import { useOrgPermission } from "@app/context"; import { OrgDeleteSection } from "../OrgDeleteSection"; import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection"; import { OrgNameChangeSection } from "../OrgNameChangeSection"; +import { OrgProductSelectSection } from "../OrgProductSelectSection"; export const OrgGeneralTab = () => { const { membership } = useOrgPermission(); return ( -
- - - {membership && membership.role === "admin" && } -
+ <> +
+ + + {membership && membership.role === "admin" && } +
+ + ); }; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx new file mode 100644 index 000000000..6f303b05e --- /dev/null +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx @@ -0,0 +1,84 @@ +import { createNotification } from "@app/components/notifications"; +import { Switch } from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { useUpdateOrg } from "@app/hooks/api"; +import { useEffect, useState } from "react"; + +export const OrgProductSelectSection = () => { + const [toggledProducts, setToggledProducts] = useState<{ + [key: string]: { name: string; enabled: boolean }; + }>({ + secretsProductEnabled: { + name: "Secrets", + enabled: true + }, + pkiProductEnabled: { + name: "PKI", + enabled: true + }, + kmsProductEnabled: { + name: "KMS", + enabled: true + }, + sshProductEnabled: { + name: "SSH", + enabled: true + } + }); + + const { currentOrg } = useOrganization(); + const { mutateAsync } = useUpdateOrg(); + + useEffect(() => { + for (const [key, value] of Object.entries(currentOrg)) { + if (key in toggledProducts && typeof value === "boolean") { + setToggledProducts((products) => ({ + ...products, + [key]: { ...products[key], enabled: value } + })); + } + } + }, [currentOrg]); + + const onProductToggle = async (value: boolean, key: string) => { + setToggledProducts((products) => ({ + ...products, + [key]: { ...products[key], enabled: value } + })); + + console.log(key, value); + + // Update backend + await mutateAsync({ + orgId: currentOrg.id, + [key]: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} ${toggledProducts[key].name}`, + type: "success" + }); + }; + + return ( +
+

Organization Products

+

+ Select which products are available for your organization. +

+ +
+ {Object.entries(toggledProducts).map(([key, product]) => ( + onProductToggle(value, key)} + isChecked={product.enabled} + > +

{product.name}

+
+ ))} +
+
+ ); +}; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/index.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/index.tsx new file mode 100644 index 000000000..fb1bdbb29 --- /dev/null +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/index.tsx @@ -0,0 +1 @@ +export { OrgProductSelectSection } from "./OrgProductSelectSection"; From d268f52a1c011f895e113e784509bc9e78f956ca Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 15 May 2025 16:50:37 -0400 Subject: [PATCH 2/8] small ui tweak --- .../OrgProductSelectSection.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx index 6f303b05e..22825f9d4 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx @@ -1,5 +1,5 @@ import { createNotification } from "@app/components/notifications"; -import { Switch } from "@app/components/v2"; +import { Checkbox, Switch } from "@app/components/v2"; import { useOrganization } from "@app/context"; import { useUpdateOrg } from "@app/hooks/api"; import { useEffect, useState } from "react"; @@ -9,11 +9,11 @@ export const OrgProductSelectSection = () => { [key: string]: { name: string; enabled: boolean }; }>({ secretsProductEnabled: { - name: "Secrets", + name: "Secret Management", enabled: true }, pkiProductEnabled: { - name: "PKI", + name: "Certificate Management", enabled: true }, kmsProductEnabled: { @@ -53,11 +53,6 @@ export const OrgProductSelectSection = () => { orgId: currentOrg.id, [key]: value }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} ${toggledProducts[key].name}`, - type: "success" - }); }; return ( @@ -67,15 +62,17 @@ export const OrgProductSelectSection = () => { Select which products are available for your organization.

-
+
{Object.entries(toggledProducts).map(([key, product]) => ( onProductToggle(value, key)} isChecked={product.enabled} + className="ml-0" + containerClassName="flex-row-reverse gap-3 w-fit" > -

{product.name}

+ {product.name}
))}
From da0b07ce2a641b1b778c835788de58fd72a91238 Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 15 May 2025 20:45:32 -0400 Subject: [PATCH 3/8] added the other two products and small UI tweaks --- .../20250515164622_select-org-products.ts | 4 +++ backend/src/db/schemas/organizations.ts | 4 ++- .../server/routes/v1/organization-router.ts | 4 ++- backend/src/services/org/org-schema.ts | 4 ++- backend/src/services/org/org-service.ts | 8 +++-- backend/src/services/org/org-types.ts | 2 ++ .../src/hooks/api/organization/queries.tsx | 8 +++-- frontend/src/hooks/api/organization/types.ts | 4 +++ .../MinimizedOrgSidebar.tsx | 36 +++++++++++-------- .../OrgProductSelectSection.tsx | 20 +++++++---- 10 files changed, 66 insertions(+), 28 deletions(-) diff --git a/backend/src/db/migrations/20250515164622_select-org-products.ts b/backend/src/db/migrations/20250515164622_select-org-products.ts index f143d7c0e..251d66208 100644 --- a/backend/src/db/migrations/20250515164622_select-org-products.ts +++ b/backend/src/db/migrations/20250515164622_select-org-products.ts @@ -8,6 +8,8 @@ export async function up(knex: Knex): Promise { t.boolean("pkiProductEnabled").defaultTo(true); t.boolean("kmsProductEnabled").defaultTo(true); t.boolean("sshProductEnabled").defaultTo(true); + t.boolean("scannerProductEnabled").defaultTo(true); + t.boolean("shareSecretsProductEnabled").defaultTo(true); }); } @@ -17,5 +19,7 @@ export async function down(knex: Knex): Promise { t.dropColumn("pkiProductEnabled"); t.dropColumn("kmsProductEnabled"); t.dropColumn("sshProductEnabled"); + t.dropColumn("scannerProductEnabled"); + t.dropColumn("shareSecretsProductEnabled"); }); } diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index 1b175f0c0..6779d5407 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -32,7 +32,9 @@ export const OrganizationsSchema = z.object({ secretsProductEnabled: z.boolean().default(true).nullable().optional(), pkiProductEnabled: z.boolean().default(true).nullable().optional(), kmsProductEnabled: z.boolean().default(true).nullable().optional(), - sshProductEnabled: z.boolean().default(true).nullable().optional() + sshProductEnabled: z.boolean().default(true).nullable().optional(), + scannerProductEnabled: z.boolean().default(true).nullable().optional(), + shareSecretsProductEnabled: z.boolean().default(true).nullable().optional() }); export type TOrganizations = z.infer; diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 991387d15..5246fe5cb 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -279,7 +279,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { secretsProductEnabled: z.boolean().optional(), pkiProductEnabled: z.boolean().optional(), kmsProductEnabled: z.boolean().optional(), - sshProductEnabled: z.boolean().optional() + sshProductEnabled: z.boolean().optional(), + scannerProductEnabled: z.boolean().optional(), + shareSecretsProductEnabled: z.boolean().optional() }), response: { 200: z.object({ diff --git a/backend/src/services/org/org-schema.ts b/backend/src/services/org/org-schema.ts index df5b86d5c..39a1680a9 100644 --- a/backend/src/services/org/org-schema.ts +++ b/backend/src/services/org/org-schema.ts @@ -22,5 +22,7 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({ secretsProductEnabled: true, pkiProductEnabled: true, kmsProductEnabled: true, - sshProductEnabled: true + sshProductEnabled: true, + scannerProductEnabled: true, + shareSecretsProductEnabled: true }); diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 7fb244c36..bcbd9e0e5 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -359,7 +359,9 @@ export const orgServiceFactory = ({ secretsProductEnabled, pkiProductEnabled, kmsProductEnabled, - sshProductEnabled + sshProductEnabled, + scannerProductEnabled, + shareSecretsProductEnabled } }: TUpdateOrgDTO) => { const appCfg = getConfig(); @@ -465,7 +467,9 @@ export const orgServiceFactory = ({ secretsProductEnabled, pkiProductEnabled, kmsProductEnabled, - sshProductEnabled + sshProductEnabled, + scannerProductEnabled, + shareSecretsProductEnabled }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); return org; diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index ea336d146..9625934fb 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -79,6 +79,8 @@ export type TUpdateOrgDTO = { pkiProductEnabled: boolean; kmsProductEnabled: boolean; sshProductEnabled: boolean; + scannerProductEnabled: boolean; + shareSecretsProductEnabled: boolean; }>; } & TOrgPermission; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 737cc6bd3..947353162 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -116,7 +116,9 @@ export const useUpdateOrg = () => { secretsProductEnabled, pkiProductEnabled, kmsProductEnabled, - sshProductEnabled + sshProductEnabled, + scannerProductEnabled, + shareSecretsProductEnabled }) => { return apiRequest.patch(`/api/v1/organization/${orgId}`, { name, @@ -132,7 +134,9 @@ export const useUpdateOrg = () => { secretsProductEnabled, pkiProductEnabled, kmsProductEnabled, - sshProductEnabled + sshProductEnabled, + scannerProductEnabled, + shareSecretsProductEnabled }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 5da571504..ab015f890 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -24,6 +24,8 @@ export type Organization = { pkiProductEnabled: boolean; kmsProductEnabled: boolean; sshProductEnabled: boolean; + scannerProductEnabled: boolean; + shareSecretsProductEnabled: boolean; }; export type UpdateOrgDTO = { @@ -42,6 +44,8 @@ export type UpdateOrgDTO = { pkiProductEnabled?: boolean; kmsProductEnabled?: boolean; sshProductEnabled?: boolean; + scannerProductEnabled?: boolean; + shareSecretsProductEnabled?: boolean; }; export type BillingDetails = { diff --git a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx b/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx index b466c33d5..61863e79e 100644 --- a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx @@ -332,21 +332,27 @@ export const MinimizedOrgSidebar = () => { )} )} -
- - {({ isActive }) => ( - - Scanner - - )} - - - {({ isActive }) => ( - - Share - - )} - + {(currentOrg.scannerProductEnabled || currentOrg.shareSecretsProductEnabled) && ( +
+ )} + {currentOrg.scannerProductEnabled && ( + + {({ isActive }) => ( + + Scanner + + )} + + )} + {currentOrg.shareSecretsProductEnabled && ( + + {({ isActive }) => ( + + Share + + )} + + )}
{ const [toggledProducts, setToggledProducts] = useState<{ @@ -23,6 +23,14 @@ export const OrgProductSelectSection = () => { sshProductEnabled: { name: "SSH", enabled: true + }, + scannerProductEnabled: { + name: "Scanner", + enabled: true + }, + shareSecretsProductEnabled: { + name: "Share Secrets", + enabled: true } }); @@ -30,14 +38,14 @@ export const OrgProductSelectSection = () => { const { mutateAsync } = useUpdateOrg(); useEffect(() => { - for (const [key, value] of Object.entries(currentOrg)) { + Object.entries(currentOrg).forEach(([key, value]) => { if (key in toggledProducts && typeof value === "boolean") { setToggledProducts((products) => ({ ...products, [key]: { ...products[key], enabled: value } })); } - } + }); }, [currentOrg]); const onProductToggle = async (value: boolean, key: string) => { @@ -62,7 +70,7 @@ export const OrgProductSelectSection = () => { Select which products are available for your organization.

-
+
{Object.entries(toggledProducts).map(([key, product]) => ( Date: Thu, 15 May 2025 20:49:01 -0400 Subject: [PATCH 4/8] remove comments --- backend/src/server/routes/v1/index.ts | 4 ++-- backend/src/server/routes/v1/organization-router.ts | 3 --- backend/src/server/routes/v3/index.ts | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 6fa6e4d54..018e457fa 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -110,7 +110,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" }); await pkiRouter.register(registerPkiSubscriberRouter, { prefix: "/subscribers" }); }, - { prefix: "/pki" } // TODO(andrey): Block this if PKI PRODUCT disabled? + { prefix: "/pki" } ); await server.register(registerProjectBotRouter, { prefix: "/bot" }); @@ -129,7 +129,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerUserEngagementRouter, { prefix: "/user-engagement" }); await server.register(registerDashboardRouter, { prefix: "/dashboard" }); - await server.register(registerCmekRouter, { prefix: "/kms" }); // TODO(andrey): Block this if KMS PRODUCT disabled? + await server.register(registerCmekRouter, { prefix: "/kms" }); await server.register(registerExternalGroupOrgRoleMappingRouter, { prefix: "/external-group-mappings" }); await server.register( diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 5246fe5cb..e14dacebb 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -292,9 +292,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - console.log("REQ BODY"); - console.log(req.body); - const organization = await server.services.org.updateOrg({ actor: req.permission.type, actorId: req.permission.id, diff --git a/backend/src/server/routes/v3/index.ts b/backend/src/server/routes/v3/index.ts index b0656f1f1..ed8401560 100644 --- a/backend/src/server/routes/v3/index.ts +++ b/backend/src/server/routes/v3/index.ts @@ -9,7 +9,7 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerSignupRouter, { prefix: "/signup" }); await server.register(registerLoginRouter, { prefix: "/auth" }); await server.register(registerUserRouter, { prefix: "/users" }); - await server.register(registerSecretRouter, { prefix: "/secrets" }); // TODO(andrey): Block this if SECRETS PRODUCT disabled? + await server.register(registerSecretRouter, { prefix: "/secrets" }); await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" }); await server.register(registerExternalMigrationRouter, { prefix: "/migrate" }); }; From 2e12d9a13c2806b79dfcfa380b1b381eb0f393ba Mon Sep 17 00:00:00 2001 From: x032205 <120525481+x032205@users.noreply.github.com> Date: Thu, 15 May 2025 21:01:30 -0400 Subject: [PATCH 5/8] Update frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx index 85fdfa132..977201a44 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx @@ -14,7 +14,7 @@ export const OrgGeneralTab = () => { {membership && membership.role === "admin" && }
- + {membership && membership.role === "admin" && } ); }; From 818778ddc52fe602db30aa8cbaab00bbdffd24c3 Mon Sep 17 00:00:00 2001 From: x032205 <120525481+x032205@users.noreply.github.com> Date: Thu, 15 May 2025 21:01:46 -0400 Subject: [PATCH 6/8] Update frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../OrgProductSelectSection/OrgProductSelectSection.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx index 91dad5c34..bd33659bd 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx @@ -54,7 +54,6 @@ export const OrgProductSelectSection = () => { [key]: { ...products[key], enabled: value } })); - console.log(key, value); // Update backend await mutateAsync({ From ae8cf06ec6635e6ef87ac61b32c4d5cff7559b5f Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 15 May 2025 21:05:39 -0400 Subject: [PATCH 7/8] greptile review fixes --- .../OrgProductSelectSection.tsx | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx index bd33659bd..b9991d0e5 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx @@ -3,6 +3,8 @@ import { useEffect, useState } from "react"; import { Switch } from "@app/components/v2"; import { useOrganization } from "@app/context"; import { useUpdateOrg } from "@app/hooks/api"; +import axios from "axios"; +import { createNotification } from "@app/components/notifications"; export const OrgProductSelectSection = () => { const [toggledProducts, setToggledProducts] = useState<{ @@ -34,6 +36,8 @@ export const OrgProductSelectSection = () => { } }); + const [isLoading, setIsLoading] = useState(false); + const { currentOrg } = useOrganization(); const { mutateAsync } = useUpdateOrg(); @@ -49,17 +53,29 @@ export const OrgProductSelectSection = () => { }, [currentOrg]); const onProductToggle = async (value: boolean, key: string) => { + setIsLoading(true); + setToggledProducts((products) => ({ ...products, [key]: { ...products[key], enabled: value } })); + try { + await mutateAsync({ + orgId: currentOrg.id, + [key]: value + }); + } catch (e) { + if (axios.isAxiosError(e)) { + const { message = "Something went wrong" } = e.response?.data as { message: string }; + createNotification({ + type: "error", + text: message + }); + } + } - // Update backend - await mutateAsync({ - orgId: currentOrg.id, - [key]: value - }); + setIsLoading(false); }; return ( @@ -74,6 +90,7 @@ export const OrgProductSelectSection = () => { onProductToggle(value, key)} isChecked={product.enabled} className="ml-0" From 517c613d058fe764e580db96bf082864ce52ece5 Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 15 May 2025 22:50:09 -0400 Subject: [PATCH 8/8] migration fix --- .../20250515164622_select-org-products.ts | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/backend/src/db/migrations/20250515164622_select-org-products.ts b/backend/src/db/migrations/20250515164622_select-org-products.ts index 251d66208..c290a4ee2 100644 --- a/backend/src/db/migrations/20250515164622_select-org-products.ts +++ b/backend/src/db/migrations/20250515164622_select-org-products.ts @@ -3,23 +3,51 @@ import { Knex } from "knex"; import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { + const columns = await knex.table(TableName.Organization).columnInfo(); + await knex.schema.alterTable(TableName.Organization, (t) => { - t.boolean("secretsProductEnabled").defaultTo(true); - t.boolean("pkiProductEnabled").defaultTo(true); - t.boolean("kmsProductEnabled").defaultTo(true); - t.boolean("sshProductEnabled").defaultTo(true); - t.boolean("scannerProductEnabled").defaultTo(true); - t.boolean("shareSecretsProductEnabled").defaultTo(true); + if (!columns.secretsProductEnabled) { + t.boolean("secretsProductEnabled").defaultTo(true); + } + if (!columns.pkiProductEnabled) { + t.boolean("pkiProductEnabled").defaultTo(true); + } + if (!columns.kmsProductEnabled) { + t.boolean("kmsProductEnabled").defaultTo(true); + } + if (!columns.sshProductEnabled) { + t.boolean("sshProductEnabled").defaultTo(true); + } + if (!columns.scannerProductEnabled) { + t.boolean("scannerProductEnabled").defaultTo(true); + } + if (!columns.shareSecretsProductEnabled) { + t.boolean("shareSecretsProductEnabled").defaultTo(true); + } }); } export async function down(knex: Knex): Promise { + const columns = await knex.table(TableName.Organization).columnInfo(); + await knex.schema.alterTable(TableName.Organization, (t) => { - t.dropColumn("secretsProductEnabled"); - t.dropColumn("pkiProductEnabled"); - t.dropColumn("kmsProductEnabled"); - t.dropColumn("sshProductEnabled"); - t.dropColumn("scannerProductEnabled"); - t.dropColumn("shareSecretsProductEnabled"); + if (columns.secretsProductEnabled) { + t.dropColumn("secretsProductEnabled"); + } + if (columns.pkiProductEnabled) { + t.dropColumn("pkiProductEnabled"); + } + if (columns.kmsProductEnabled) { + t.dropColumn("kmsProductEnabled"); + } + if (columns.sshProductEnabled) { + t.dropColumn("sshProductEnabled"); + } + if (columns.scannerProductEnabled) { + t.dropColumn("scannerProductEnabled"); + } + if (columns.shareSecretsProductEnabled) { + t.dropColumn("shareSecretsProductEnabled"); + } }); }