Add mysql stuff

Add missing mysql types

Add missing mysql import

Add more mysql stuff

Add mysql options

Add missing mysql stuff

Update more for mysql stuff

Fix wrong psql stuff

Add mysql as deps

Use mysql2 driver

Add missing mysql stuff

Fix mysql form

Fix db conn details schema

Add more missing stuff for mysql

Add more missing stuff

Add TODO

Refactor

refactor

Refactor code

Refactor

Connect only for mysql

Upgrade mysql2

Try built in approach

Rename the field

Make ssl default

Clean up

Remove dev code

Add todo

Rename the right one

Fix linter

Add missing await

Fix linter issue

Fix linter complains

Fix linter complains

Fix more linters

Fix more linter issues

Fix linter issues

Fix linters

# Conflicts:
#	backend/src/ee/routes/v1/pam-resource-routers/pam-resource-router.ts
#	backend/src/ee/services/pam-resource/pam-resource-fns.ts
#	frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx

# Conflicts:
#	frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx
This commit is contained in:
Fang-Pen Lin
2025-10-24 12:29:21 -07:00
committed by Fang-Pen Lin
parent abd4cb6217
commit cc6f0e79dd
25 changed files with 561 additions and 85 deletions
+1
View File
@@ -1,5 +1,6 @@
export enum PamResourceType {
Postgres = "postgres",
MySQL = "mysql",
RDP = "rdp",
SSH = "ssh",
Kubernetes = "kubernetes"
+1
View File
@@ -5,6 +5,7 @@ export const PAM_RESOURCE_TYPE_MAP: Record<
{ name: string; image: string; size?: number }
> = {
[PamResourceType.Postgres]: { name: "PostgreSQL", image: "Postgres.png" },
[PamResourceType.MySQL]: { name: "MySQL", image: "MySql.png" },
[PamResourceType.RDP]: { name: "RDP", image: "RDP.png" },
[PamResourceType.SSH]: { name: "SSH", image: "SSH.png" },
[PamResourceType.Kubernetes]: { name: "Kubernetes", image: "Kubernetes.png" }
+4 -2
View File
@@ -1,11 +1,13 @@
import { PamResourceType, PamSessionStatus } from "../enums";
import { TPostgresAccount, TPostgresResource } from "./postgres-resource";
import { TMySQLAccount, TMySQLResource } from "./mysql-resource";
export * from "./postgres-resource";
export * from "./mysql-resource";
export type TPamResource = TPostgresResource;
export type TPamResource = TPostgresResource | TMySQLResource;
export type TPamAccount = TPostgresAccount;
export type TPamAccount = TPostgresAccount | TMySQLAccount;
export type TPamFolder = {
id: string;
@@ -0,0 +1,14 @@
import { PamResourceType } from "../enums";
import { TBaseSqlConnectionDetails, TBaseSqlCredentials } from "./shared/sql-resource";
import { TBasePamAccount } from "./base-account";
import { TBasePamResource } from "./base-resource";
// Resources
export type TMySQLResource = TBasePamResource & { resourceType: PamResourceType.MySQL } & {
connectionDetails: TBaseSqlConnectionDetails;
};
// Accounts
export type TMySQLAccount = TBasePamAccount & {
credentials: TBaseSqlCredentials;
};
@@ -0,0 +1,94 @@
import { useEffect, useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button, ModalClose } from "@app/components/v2";
import { PamResourceType, TMySQLAccount, useGetPamResourceById } from "@app/hooks/api/pam";
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
import { BaseSqlAccountSchema } from "./shared/sql-account-schemas";
import { SqlAccountFields } from "./shared/SqlAccountFields";
import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields";
import { RotateAccountFields, rotateAccountFieldsSchema } from "./RotateAccountFields";
type Props = {
account?: TMySQLAccount;
resourceId?: string;
resourceType?: PamResourceType;
onSubmit: (formData: FormData) => Promise<void>;
};
const formSchema = genericAccountFieldsSchema.extend(rotateAccountFieldsSchema.shape).extend({
credentials: BaseSqlAccountSchema
});
type FormData = z.infer<typeof formSchema>;
export const MySQLAccountForm = ({ account, resourceId, resourceType, onSubmit }: Props) => {
const isUpdate = Boolean(account);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: account
? {
...account,
credentials: {
...account.credentials,
password: UNCHANGED_PASSWORD_SENTINEL
}
}
: undefined
});
const {
handleSubmit,
formState: { isSubmitting, isDirty }
} = form;
const [rotationCredentialsConfigured, setRotationCredentialsConfigured] = useState(false);
const { data: resource } = useGetPamResourceById(resourceType, resourceId, {
enabled: !account && !!resourceId && !!resourceType
});
useEffect(() => {
if (account) {
setRotationCredentialsConfigured(account.resource.rotationCredentialsConfigured);
} else {
setRotationCredentialsConfigured(!!resource?.rotationAccountCredentials);
}
}, [account, resource]);
return (
<FormProvider {...form}>
<form
onSubmit={(e) => {
handleSubmit(onSubmit)(e);
}}
>
<GenericAccountFields />
<SqlAccountFields isUpdate={isUpdate} />
<RotateAccountFields rotationCredentialsConfigured={rotationCredentialsConfigured} />
<div className="mt-6 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
{isUpdate ? "Update Account" : "Create Account"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};
@@ -8,6 +8,7 @@ import {
import { DiscriminativePick } from "@app/types";
import { PamAccountHeader } from "../PamAccountHeader";
import { MySQLAccountForm } from "./MySQLAccountForm";
import { PostgresAccountForm } from "./PostgresAccountForm";
type FormProps = {
@@ -72,6 +73,13 @@ const CreateForm = ({
resourceType={resourceType}
/>
);
case PamResourceType.MySQL:
return (
<MySQLAccountForm
onSubmit={onSubmit}
resourceId={resourceId}
resourceType={resourceType}
/>);
default:
throw new Error(`Unhandled resource: ${resourceType}`);
}
@@ -110,6 +118,8 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => {
switch (account.resource.resourceType) {
case PamResourceType.Postgres:
return <PostgresAccountForm account={account} onSubmit={onSubmit} />;
case PamResourceType.MySQL:
return <MySQLAccountForm account={account} onSubmit={onSubmit} />;
default:
throw new Error(`Unhandled resource: ${account.resource.resourceType}`);
}
@@ -0,0 +1,84 @@
import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button, ModalClose } from "@app/components/v2";
import { PamResourceType, TMySQLResource } from "@app/hooks/api/pam";
import { BaseSqlResourceSchema } from "./shared/sql-resource-schemas";
import { SqlResourceFields } from "./shared/SqlResourceFields";
import { GenericResourceFields, genericResourceFieldsSchema } from "./GenericResourceFields";
type Props = {
resource?: TMySQLResource;
onSubmit: (formData: FormData) => Promise<void>;
};
const formSchema = genericResourceFieldsSchema.extend({
resourceType: z.literal(PamResourceType.MySQL),
connectionDetails: BaseSqlResourceSchema.extend({
database: z.string().trim().optional().default("")
})
});
type FormData = z.infer<typeof formSchema>;
export const MySQLResourceForm = ({ resource, onSubmit }: Props) => {
const isUpdate = Boolean(resource);
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: resource ?? {
resourceType: PamResourceType.MySQL,
connectionDetails: {
host: "",
port: 3306,
database: "",
sslEnabled: true,
sslRejectUnauthorized: true,
sslCertificate: undefined
}
}
});
const {
handleSubmit,
formState: { isSubmitting, isDirty }
} = form;
return (
<FormProvider {...form}>
<form
onSubmit={(e) => {
setSelectedTabIndex(0);
handleSubmit(onSubmit)(e);
}}
>
<GenericResourceFields />
<SqlResourceFields
selectedTabIndex={selectedTabIndex}
setSelectedTabIndex={setSelectedTabIndex}
/>
<div className="mt-6 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
{isUpdate ? "Update Details" : "Create Resource"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};
@@ -9,6 +9,7 @@ import {
import { DiscriminativePick } from "@app/types";
import { PamResourceHeader } from "../PamResourceHeader";
import { MySQLResourceForm } from "./MySQLResourceForm";
import { PostgresResourceForm } from "./PostgresResourceForm";
type FormProps = {
@@ -57,6 +58,8 @@ const CreateForm = ({ resourceType, onComplete, projectId }: CreateFormProps) =>
switch (resourceType) {
case PamResourceType.Postgres:
return <PostgresResourceForm onSubmit={onSubmit} />;
case PamResourceType.MySQL:
return <MySQLResourceForm onSubmit={onSubmit} />;
default:
throw new Error(`Unhandled resource: ${resourceType}`);
}
@@ -92,8 +95,10 @@ const UpdateForm = ({ resource, onComplete }: UpdateFormProps) => {
switch (resource.resourceType) {
case PamResourceType.Postgres:
return <PostgresResourceForm resource={resource} onSubmit={onSubmit} />;
case PamResourceType.MySQL:
return <MySQLResourceForm resource={resource} onSubmit={onSubmit} />;
default:
throw new Error(`Unhandled resource: ${resource.resourceType}`);
throw new Error(`Unhandled resource: ${(resource as any).resourceType}`);
}
};
@@ -110,7 +110,7 @@ export const SqlResourceFields = ({ setSelectedTabIndex, selectedTabIndex }: Pro
errorText={error?.message}
isError={Boolean(error?.message)}
className={sslEnabled ? "" : "opacity-50"}
label="SSL Certificate"
label="Trusted CA SSL Certificate"
isOptional
>
<TextArea className="h-14 resize-none!" {...field} isDisabled={!sslEnabled} />