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]) => (