mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 09:28:28 +00:00
feat: completed migration of ssh product
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -79,6 +79,12 @@ export const ROUTE_PATHS = Object.freeze({
|
|||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
Ssh: {
|
||||||
|
SshCaByIDPage: setRoute(
|
||||||
|
"/ssh/$projectId/ca/$caId",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId"
|
||||||
|
)
|
||||||
|
},
|
||||||
Public: {
|
Public: {
|
||||||
ViewSharedSecretByIDPage: setRoute("/shared/secret/$secretId", "/shared/secret/$secretId")
|
ViewSharedSecretByIDPage: setRoute("/shared/secret/$secretId", "/shared/secret/$secretId")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ export enum ProjectPermissionSub {
|
|||||||
CertificateAuthorities = "certificate-authorities",
|
CertificateAuthorities = "certificate-authorities",
|
||||||
Certificates = "certificates",
|
Certificates = "certificates",
|
||||||
CertificateTemplates = "certificate-templates",
|
CertificateTemplates = "certificate-templates",
|
||||||
|
SshCertificateAuthorities = "ssh-certificate-authorities",
|
||||||
|
SshCertificateTemplates = "ssh-certificate-templates",
|
||||||
|
SshCertificates = "ssh-certificates",
|
||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
@@ -165,6 +168,9 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ export const getProjectTitle = (type: ProjectType) => {
|
|||||||
const titleConvert = {
|
const titleConvert = {
|
||||||
[ProjectType.SecretManager]: "Secret Management",
|
[ProjectType.SecretManager]: "Secret Management",
|
||||||
[ProjectType.KMS]: "Key Management",
|
[ProjectType.KMS]: "Key Management",
|
||||||
[ProjectType.CertificateManager]: "Cert Management"
|
[ProjectType.CertificateManager]: "Cert Management",
|
||||||
|
[ProjectType.SSH]: "SSH"
|
||||||
};
|
};
|
||||||
return titleConvert[type];
|
return titleConvert[type];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { SshCaStatus } from "../sshCa";
|
||||||
|
import { SshCertTemplateStatus } from "../sshCertificateTemplates";
|
||||||
import { CaStatus, CaType } from "./enums";
|
import { CaStatus, CaType } from "./enums";
|
||||||
|
|
||||||
export const caTypeToNameMap: { [K in CaType]: string } = {
|
export const caTypeToNameMap: { [K in CaType]: string } = {
|
||||||
@@ -11,7 +13,7 @@ export const caStatusToNameMap: { [K in CaStatus]: string } = {
|
|||||||
[CaStatus.PENDING_CERTIFICATE]: "Pending Certificate"
|
[CaStatus.PENDING_CERTIFICATE]: "Pending Certificate"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCaStatusBadgeVariant = (status: CaStatus) => {
|
export const getCaStatusBadgeVariant = (status: CaStatus | SshCaStatus | SshCertTemplateStatus) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case CaStatus.ACTIVE:
|
case CaStatus.ACTIVE:
|
||||||
return "success";
|
return "success";
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export * from "./secretSharing";
|
|||||||
export * from "./secretSnapshots";
|
export * from "./secretSnapshots";
|
||||||
export * from "./serverDetails";
|
export * from "./serverDetails";
|
||||||
export * from "./serviceTokens";
|
export * from "./serviceTokens";
|
||||||
|
export * from "./sshCa";
|
||||||
|
export * from "./sshCertificateTemplates";
|
||||||
export * from "./ssoConfig";
|
export * from "./ssoConfig";
|
||||||
export * from "./subscriptions";
|
export * from "./subscriptions";
|
||||||
export * from "./tags";
|
export * from "./tags";
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export enum SshCaStatus {
|
||||||
|
ACTIVE = "active",
|
||||||
|
DISABLED = "disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SshCertType {
|
||||||
|
USER = "user",
|
||||||
|
HOST = "host"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sshCertTypeToNameMap: { [K in SshCertType]: string } = {
|
||||||
|
[SshCertType.USER]: "User",
|
||||||
|
[SshCertType.HOST]: "Host"
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export { SshCaStatus } from "./constants";
|
||||||
|
export {
|
||||||
|
useCreateSshCa,
|
||||||
|
useDeleteSshCa,
|
||||||
|
useIssueSshCreds,
|
||||||
|
useSignSshKey,
|
||||||
|
useUpdateSshCa
|
||||||
|
} from "./mutations";
|
||||||
|
export { useGetSshCaById, useGetSshCaCertTemplates } from "./queries";
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { workspaceKeys } from "../workspace/query-keys";
|
||||||
|
import {
|
||||||
|
TCreateSshCaDTO,
|
||||||
|
TDeleteSshCaDTO,
|
||||||
|
TIssueSshCredsDTO,
|
||||||
|
TIssueSshCredsResponse,
|
||||||
|
TSignSshKeyDTO,
|
||||||
|
TSignSshKeyResponse,
|
||||||
|
TSshCertificateAuthority,
|
||||||
|
TUpdateSshCaDTO
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export const sshCaKeys = {
|
||||||
|
getSshCaById: (caId: string) => [{ caId }, "ssh-ca"]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreateSshCa = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TSshCertificateAuthority, object, TCreateSshCaDTO>({
|
||||||
|
mutationFn: async (body) => {
|
||||||
|
const {
|
||||||
|
data: { ca }
|
||||||
|
} = await apiRequest.post<{ ca: TSshCertificateAuthority }>("/api/v1/ssh/ca/", body);
|
||||||
|
return ca;
|
||||||
|
},
|
||||||
|
onSuccess: ({ projectId }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshCas(projectId) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateSshCa = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TSshCertificateAuthority, object, TUpdateSshCaDTO>({
|
||||||
|
mutationFn: async ({ caId, ...body }) => {
|
||||||
|
const {
|
||||||
|
data: { ca }
|
||||||
|
} = await apiRequest.patch<{ ca: TSshCertificateAuthority }>(`/api/v1/ssh/ca/${caId}`, body);
|
||||||
|
return ca;
|
||||||
|
},
|
||||||
|
onSuccess: ({ projectId }, { caId }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshCas(projectId) });
|
||||||
|
queryClient.invalidateQueries({ queryKey: sshCaKeys.getSshCaById(caId) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteSshCa = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TSshCertificateAuthority, object, TDeleteSshCaDTO>({
|
||||||
|
mutationFn: async ({ caId }) => {
|
||||||
|
const {
|
||||||
|
data: { ca }
|
||||||
|
} = await apiRequest.delete<{ ca: TSshCertificateAuthority }>(`/api/v1/ssh/ca/${caId}`);
|
||||||
|
return ca;
|
||||||
|
},
|
||||||
|
onSuccess: ({ projectId }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshCas(projectId) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSignSshKey = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TSignSshKeyResponse, object, TSignSshKeyDTO>({
|
||||||
|
mutationFn: async (body) => {
|
||||||
|
const { data } = await apiRequest.post<TSignSshKeyResponse>(
|
||||||
|
"/api/v1/ssh/certificates/sign",
|
||||||
|
body
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { projectId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: workspaceKeys.allWorkspaceSshCertificates(projectId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useIssueSshCreds = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TIssueSshCredsResponse, object, TIssueSshCredsDTO>({
|
||||||
|
mutationFn: async (body) => {
|
||||||
|
const { data } = await apiRequest.post<TIssueSshCredsResponse>(
|
||||||
|
"/api/v1/ssh/certificates/issue",
|
||||||
|
body
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { projectId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: workspaceKeys.allWorkspaceSshCertificates(projectId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
|
||||||
|
import { TSshCertificateAuthority } from "./types";
|
||||||
|
|
||||||
|
export const sshCaKeys = {
|
||||||
|
getSshCaById: (caId: string) => [{ caId }, "ssh-ca"],
|
||||||
|
getSshCaCertTemplates: (caId: string) => [{ caId }, "ssh-ca-cert-templates"]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSshCaById = (caId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: sshCaKeys.getSshCaById(caId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { ca }
|
||||||
|
} = await apiRequest.get<{ ca: TSshCertificateAuthority }>(`/api/v1/ssh/ca/${caId}`);
|
||||||
|
return ca;
|
||||||
|
},
|
||||||
|
enabled: Boolean(caId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSshCaCertTemplates = (caId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: sshCaKeys.getSshCaCertTemplates(caId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{
|
||||||
|
certificateTemplates: TSshCertificateTemplate[];
|
||||||
|
}>(`/api/v1/ssh/ca/${caId}/certificate-templates`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: Boolean(caId)
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { CertKeyAlgorithm } from "../certificates/enums";
|
||||||
|
import { SshCaStatus, SshCertType } from "./constants";
|
||||||
|
|
||||||
|
export type TSshCertificate = {
|
||||||
|
id: string;
|
||||||
|
sshCaId: string;
|
||||||
|
sshCertificateTemplateId: string;
|
||||||
|
serialNumber: string;
|
||||||
|
certType: SshCertType;
|
||||||
|
principals: string[];
|
||||||
|
keyId: string;
|
||||||
|
notBefore: string;
|
||||||
|
notAfter: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSshCertificateAuthority = {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
status: SshCaStatus;
|
||||||
|
friendlyName: string;
|
||||||
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
publicKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCreateSshCaDTO = {
|
||||||
|
projectId: string;
|
||||||
|
friendlyName?: string;
|
||||||
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateSshCaDTO = {
|
||||||
|
caId: string;
|
||||||
|
friendlyName?: string;
|
||||||
|
status?: SshCaStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteSshCaDTO = {
|
||||||
|
caId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSignSshKeyDTO = {
|
||||||
|
projectId: string;
|
||||||
|
certificateTemplateId: string;
|
||||||
|
publicKey?: string;
|
||||||
|
certType: SshCertType;
|
||||||
|
principals: string[];
|
||||||
|
ttl?: string;
|
||||||
|
keyId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSignSshKeyResponse = {
|
||||||
|
serialNumber: string;
|
||||||
|
signedKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueSshCredsDTO = {
|
||||||
|
projectId: string;
|
||||||
|
certificateTemplateId: string;
|
||||||
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
certType: SshCertType;
|
||||||
|
principals: string[];
|
||||||
|
ttl?: string;
|
||||||
|
keyId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueSshCredsResponse = {
|
||||||
|
serialNumber: string;
|
||||||
|
signedKey: string;
|
||||||
|
privateKey: string;
|
||||||
|
publicKey: string;
|
||||||
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export {
|
||||||
|
useCreateSshCertTemplate,
|
||||||
|
useDeleteSshCertTemplate,
|
||||||
|
useUpdateSshCertTemplate
|
||||||
|
} from "./mutations";
|
||||||
|
export { useGetSshCertTemplate } from "./queries";
|
||||||
|
export * from "./types";
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { sshCaKeys } from "../sshCa/queries";
|
||||||
|
import {
|
||||||
|
TCreateSshCertificateTemplateDTO,
|
||||||
|
TDeleteSshCertificateTemplateDTO,
|
||||||
|
TSshCertificateTemplate,
|
||||||
|
TUpdateSshCertificateTemplateDTO
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export const useCreateSshCertTemplate = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TSshCertificateTemplate, object, TCreateSshCertificateTemplateDTO>({
|
||||||
|
mutationFn: async (data) => {
|
||||||
|
const { data: certificateTemplate } = await apiRequest.post<TSshCertificateTemplate>(
|
||||||
|
"/api/v1/ssh/certificate-templates",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return certificateTemplate;
|
||||||
|
},
|
||||||
|
onSuccess: ({ sshCaId }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: sshCaKeys.getSshCaCertTemplates(sshCaId) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateSshCertTemplate = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TSshCertificateTemplate, object, TUpdateSshCertificateTemplateDTO>({
|
||||||
|
mutationFn: async (data) => {
|
||||||
|
const { data: certificateTemplate } = await apiRequest.patch<TSshCertificateTemplate>(
|
||||||
|
`/api/v1/ssh/certificate-templates/${data.id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
return certificateTemplate;
|
||||||
|
},
|
||||||
|
onSuccess: ({ sshCaId }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: sshCaKeys.getSshCaCertTemplates(sshCaId) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteSshCertTemplate = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TSshCertificateTemplate, object, TDeleteSshCertificateTemplateDTO>({
|
||||||
|
mutationFn: async (data) => {
|
||||||
|
const { data: certificateTemplate } = await apiRequest.delete<TSshCertificateTemplate>(
|
||||||
|
`/api/v1/ssh/certificate-templates/${data.id}`
|
||||||
|
);
|
||||||
|
return certificateTemplate;
|
||||||
|
},
|
||||||
|
onSuccess: ({ sshCaId }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: sshCaKeys.getSshCaCertTemplates(sshCaId) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { TSshCertificateTemplate } from "./types";
|
||||||
|
|
||||||
|
export const certTemplateKeys = {
|
||||||
|
getSshCertTemplateById: (id: string) => [{ id }, "ssh-cert-template"]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSshCertTemplate = (id: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: certTemplateKeys.getSshCertTemplateById(id),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data: certificateTemplate } = await apiRequest.get<TSshCertificateTemplate>(
|
||||||
|
`/api/v1/ssh/certificate-templates/${id}`
|
||||||
|
);
|
||||||
|
return certificateTemplate;
|
||||||
|
},
|
||||||
|
enabled: Boolean(id)
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
export enum SshCertTemplateStatus {
|
||||||
|
ACTIVE = "active",
|
||||||
|
DISABLED = "disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TSshCertificateTemplate = {
|
||||||
|
id: string;
|
||||||
|
sshCaId: string;
|
||||||
|
status: SshCertTemplateStatus;
|
||||||
|
name: string;
|
||||||
|
ttl: string;
|
||||||
|
maxTTL: string;
|
||||||
|
allowedUsers: string[];
|
||||||
|
allowedHosts: string[];
|
||||||
|
allowUserCertificates: boolean;
|
||||||
|
allowHostCertificates: boolean;
|
||||||
|
allowCustomKeyIds: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCreateSshCertificateTemplateDTO = {
|
||||||
|
sshCaId: string;
|
||||||
|
name: string;
|
||||||
|
ttl: string;
|
||||||
|
maxTTL: string;
|
||||||
|
allowedUsers: string[];
|
||||||
|
allowedHosts: string[];
|
||||||
|
allowUserCertificates: boolean;
|
||||||
|
allowHostCertificates: boolean;
|
||||||
|
allowCustomKeyIds: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateSshCertificateTemplateDTO = {
|
||||||
|
id: string;
|
||||||
|
status?: SshCertTemplateStatus;
|
||||||
|
name?: string;
|
||||||
|
ttl?: string;
|
||||||
|
maxTTL?: string;
|
||||||
|
allowedUsers?: string[];
|
||||||
|
allowedHosts?: string[];
|
||||||
|
allowUserCertificates?: boolean;
|
||||||
|
allowHostCertificates?: boolean;
|
||||||
|
allowCustomKeyIds?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteSshCertificateTemplateDTO = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
@@ -32,6 +32,9 @@ export {
|
|||||||
useListWorkspaceGroups,
|
useListWorkspaceGroups,
|
||||||
useListWorkspacePkiAlerts,
|
useListWorkspacePkiAlerts,
|
||||||
useListWorkspacePkiCollections,
|
useListWorkspacePkiCollections,
|
||||||
|
useListWorkspaceSshCas,
|
||||||
|
useListWorkspaceSshCertificates,
|
||||||
|
useListWorkspaceSshCertificateTemplates,
|
||||||
useNameWorkspaceSecrets,
|
useNameWorkspaceSecrets,
|
||||||
useToggleAutoCapitalization,
|
useToggleAutoCapitalization,
|
||||||
useUpdateIdentityWorkspaceRole,
|
useUpdateIdentityWorkspaceRole,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { TIntegration } from "../integrations/types";
|
|||||||
import { TPkiAlert } from "../pkiAlerts/types";
|
import { TPkiAlert } from "../pkiAlerts/types";
|
||||||
import { TPkiCollection } from "../pkiCollections/types";
|
import { TPkiCollection } from "../pkiCollections/types";
|
||||||
import { EncryptedSecret } from "../secrets/types";
|
import { EncryptedSecret } from "../secrets/types";
|
||||||
|
import { TSshCertificate, TSshCertificateAuthority } from "../sshCa/types";
|
||||||
|
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
|
||||||
import { userKeys } from "../users/query-keys";
|
import { userKeys } from "../users/query-keys";
|
||||||
import { TWorkspaceUser } from "../users/types";
|
import { TWorkspaceUser } from "../users/types";
|
||||||
import { ProjectSlackConfig } from "../workflowIntegrations/types";
|
import { ProjectSlackConfig } from "../workflowIntegrations/types";
|
||||||
@@ -758,6 +760,67 @@ export const useListWorkspaceCertificateTemplates = ({ workspaceId }: { workspac
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useListWorkspaceSshCertificates = ({
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
projectId
|
||||||
|
}: {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
projectId: string;
|
||||||
|
}) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.specificWorkspaceSshCertificates({
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
projectId
|
||||||
|
}),
|
||||||
|
queryFn: async () => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
offset: String(offset),
|
||||||
|
limit: String(limit)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await apiRequest.get<{
|
||||||
|
certificates: TSshCertificate[];
|
||||||
|
totalCount: number;
|
||||||
|
}>(`/api/v2/workspace/${projectId}/ssh-certificates`, {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: Boolean(projectId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useListWorkspaceSshCas = (projectId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.getWorkspaceSshCas(projectId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { cas }
|
||||||
|
} = await apiRequest.get<{ cas: Omit<TSshCertificateAuthority, "publicKey">[] }>(
|
||||||
|
`/api/v2/workspace/${projectId}/ssh-cas`
|
||||||
|
);
|
||||||
|
return cas;
|
||||||
|
},
|
||||||
|
enabled: Boolean(projectId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useListWorkspaceSshCertificateTemplates = (projectId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.getWorkspaceSshCertificateTemplates(projectId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{ certificateTemplates: TSshCertificateTemplate[] }>(
|
||||||
|
`/api/v2/workspace/${projectId}/ssh-certificate-templates`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: Boolean(projectId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetWorkspaceSlackConfig = ({ workspaceId }: { workspaceId: string }) => {
|
export const useGetWorkspaceSlackConfig = ({ workspaceId }: { workspaceId: string }) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: workspaceKeys.getWorkspaceSlackConfig(workspaceId),
|
queryKey: workspaceKeys.getWorkspaceSlackConfig(workspaceId),
|
||||||
|
|||||||
@@ -53,5 +53,19 @@ export const workspaceKeys = {
|
|||||||
getWorkspaceCertificateTemplates: (workspaceId: string) =>
|
getWorkspaceCertificateTemplates: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-certificate-templates"] as const,
|
[{ workspaceId }, "workspace-certificate-templates"] as const,
|
||||||
getWorkspaceSlackConfig: (workspaceId: string) =>
|
getWorkspaceSlackConfig: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-slack-config"] as const
|
[{ workspaceId }, "workspace-slack-config"] as const,
|
||||||
|
getWorkspaceSshCas: (projectId: string) => [{ projectId }, "workspace-ssh-cas"] as const,
|
||||||
|
allWorkspaceSshCertificates: (projectId: string) =>
|
||||||
|
[{ projectId }, "workspace-ssh-certificates"] as const,
|
||||||
|
specificWorkspaceSshCertificates: ({
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
projectId
|
||||||
|
}: {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
projectId: string;
|
||||||
|
}) => [...workspaceKeys.allWorkspaceSshCertificates(projectId), { offset, limit }] as const,
|
||||||
|
getWorkspaceSshCertificateTemplates: (projectId: string) =>
|
||||||
|
[{ projectId }, "workspace-ssh-certificate-templates"] as const
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export enum ProjectVersion {
|
|||||||
export enum ProjectType {
|
export enum ProjectType {
|
||||||
SecretManager = "secret-manager",
|
SecretManager = "secret-manager",
|
||||||
CertificateManager = "cert-manager",
|
CertificateManager = "cert-manager",
|
||||||
KMS = "kms"
|
KMS = "kms",
|
||||||
|
SSH = "ssh"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectUserMembershipTemporaryMode {
|
export enum ProjectUserMembershipTemporaryMode {
|
||||||
|
|||||||
@@ -92,6 +92,16 @@ export const OrganizationLayout = () => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem
|
||||||
|
isSelected={isActive}
|
||||||
|
icon="system-regular-126-verified-hover-verified"
|
||||||
|
>
|
||||||
|
SSH
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
<Link to="/organization/access-management">
|
<Link to="/organization/access-management">
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<MenuItem isSelected={isActive} icon="system-outline-96-groups">
|
<MenuItem isSelected={isActive} icon="system-outline-96-groups">
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export const ProjectLayout = () => {
|
|||||||
const isSecretManager = currentWorkspace?.type === ProjectType.SecretManager;
|
const isSecretManager = currentWorkspace?.type === ProjectType.SecretManager;
|
||||||
const isCertManager = currentWorkspace?.type === ProjectType.CertificateManager;
|
const isCertManager = currentWorkspace?.type === ProjectType.CertificateManager;
|
||||||
const isCmek = currentWorkspace?.type === ProjectType.KMS;
|
const isCmek = currentWorkspace?.type === ProjectType.KMS;
|
||||||
|
const isSSH = currentWorkspace?.type === ProjectType.SSH;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -133,6 +134,20 @@ export const ProjectLayout = () => {
|
|||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
{isSSH && (
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.SSH}/$projectId/overview` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="system-outline-90-lock-closed">
|
||||||
|
Overview
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<Link
|
<Link
|
||||||
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
|
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
|
||||||
params={{
|
params={{
|
||||||
|
|||||||
+5
-2
@@ -53,7 +53,8 @@ enum ProjectOrderBy {
|
|||||||
const formatTitle = (type: ProjectType) => {
|
const formatTitle = (type: ProjectType) => {
|
||||||
if (type === ProjectType.SecretManager) return "Secret Management";
|
if (type === ProjectType.SecretManager) return "Secret Management";
|
||||||
if (type === ProjectType.CertificateManager) return "Cert Management";
|
if (type === ProjectType.CertificateManager) return "Cert Management";
|
||||||
return "Key Management";
|
if (type === ProjectType.KMS) return "Key Management";
|
||||||
|
return "SSH";
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDescription = (type: ProjectType) => {
|
const formatDescription = (type: ProjectType) => {
|
||||||
@@ -61,7 +62,9 @@ const formatDescription = (type: ProjectType) => {
|
|||||||
return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
|
return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
|
||||||
if (type === ProjectType.CertificateManager)
|
if (type === ProjectType.CertificateManager)
|
||||||
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
|
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
|
||||||
return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
if (type === ProjectType.KMS)
|
||||||
|
return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
||||||
|
return "Generate SSH credentials to provide secure and centralized SSH access control for your infrastructure.";
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
|
import { ProductOverviewPage } from "../SecretManagerOverviewPage/SecretManagerOverviewPage";
|
||||||
|
|
||||||
|
export const SshOverviewPage = () => <ProductOverviewPage type={ProjectType.SSH} />;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
import { SshOverviewPage } from './SshOverviewPage'
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
'/_authenticate/_inject-org-details/organization/_layout/ssh/overview',
|
||||||
|
)({
|
||||||
|
component: SshOverviewPage,
|
||||||
|
})
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||||
|
|
||||||
|
import { AccessControlPage } from "./AccessControlPage";
|
||||||
|
|
||||||
|
const AccessControlPageQuerySchema = z.object({
|
||||||
|
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management"
|
||||||
|
)({
|
||||||
|
component: AccessControlPage,
|
||||||
|
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId"
|
||||||
|
)({
|
||||||
|
component: IdentityDetailsByIDPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId"
|
||||||
|
)({
|
||||||
|
component: MemberDetailsByIDPage
|
||||||
|
});
|
||||||
+32
@@ -126,6 +126,11 @@ export const projectRoleFormSchema = z.object({
|
|||||||
[ProjectPermissionSub.PkiAlerts]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.PkiAlerts]: GeneralPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.PkiCollections]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.PkiCollections]: GeneralPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.CertificateTemplates]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.CertificateTemplates]: GeneralPolicyActionSchema.array().default([]),
|
||||||
|
[ProjectPermissionSub.SshCertificateAuthorities]: GeneralPolicyActionSchema.array().default(
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
[ProjectPermissionSub.SshCertificates]: GeneralPolicyActionSchema.array().default([]),
|
||||||
|
[ProjectPermissionSub.SshCertificateTemplates]: GeneralPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.SecretApproval]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.SecretApproval]: GeneralPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.SecretRollback]: SecretRollbackPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.SecretRollback]: SecretRollbackPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.Project]: WorkspacePolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.Project]: WorkspacePolicyActionSchema.array().default([]),
|
||||||
@@ -596,6 +601,33 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
{ label: "Remove", value: "delete" }
|
{ label: "Remove", value: "delete" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
[ProjectPermissionSub.SshCertificateAuthorities]: {
|
||||||
|
title: "SSH Certificate Authorities",
|
||||||
|
actions: [
|
||||||
|
{ label: "Read", value: "read" },
|
||||||
|
{ label: "Create", value: "create" },
|
||||||
|
{ label: "Modify", value: "edit" },
|
||||||
|
{ label: "Remove", value: "delete" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[ProjectPermissionSub.SshCertificates]: {
|
||||||
|
title: "SSH Certificates",
|
||||||
|
actions: [
|
||||||
|
{ label: "Read", value: "read" },
|
||||||
|
{ label: "Create", value: "create" },
|
||||||
|
{ label: "Modify", value: "edit" },
|
||||||
|
{ label: "Remove", value: "delete" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[ProjectPermissionSub.SshCertificateTemplates]: {
|
||||||
|
title: "SSH Certificate Templates",
|
||||||
|
actions: [
|
||||||
|
{ label: "Read", value: "read" },
|
||||||
|
{ label: "Create", value: "create" },
|
||||||
|
{ label: "Modify", value: "edit" },
|
||||||
|
{ label: "Remove", value: "delete" }
|
||||||
|
]
|
||||||
|
},
|
||||||
[ProjectPermissionSub.PkiCollections]: {
|
[ProjectPermissionSub.PkiCollections]: {
|
||||||
title: "PKI Collections",
|
title: "PKI Collections",
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug"
|
||||||
|
)({
|
||||||
|
component: RoleDetailsBySlugPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
|
||||||
|
import { SshCaSection, SshCertificatesSection } from "./components";
|
||||||
|
|
||||||
|
enum TabSections {
|
||||||
|
SshCa = "ssh-certificate-authorities",
|
||||||
|
SshCertificates = "ssh-certificates"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OverviewPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
</Helmet>
|
||||||
|
<div className="h-full bg-bunker-800">
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
|
||||||
|
<p className="mb-4 mr-4 text-3xl font-semibold text-white">SSH</p>
|
||||||
|
<Tabs defaultValue={TabSections.SshCertificates}>
|
||||||
|
<TabList>
|
||||||
|
<Tab value={TabSections.SshCertificates}>SSH Certificates</Tab>
|
||||||
|
<Tab value={TabSections.SshCa}>Certificate Authorities</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel value={TabSections.SshCertificates}>
|
||||||
|
<motion.div
|
||||||
|
key="panel-ssh-certificate-s"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Read}
|
||||||
|
a={ProjectPermissionSub.SshCertificates}
|
||||||
|
renderGuardBanner
|
||||||
|
passThrough={false}
|
||||||
|
>
|
||||||
|
<SshCertificatesSection />
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.SshCa}>
|
||||||
|
<motion.div
|
||||||
|
key="panel-ssh-certificate-authorities"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Read}
|
||||||
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
|
renderGuardBanner
|
||||||
|
passThrough={false}
|
||||||
|
>
|
||||||
|
<SshCaSection />
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import { useCreateSshCa, useGetSshCaById, useUpdateSshCa } from "@app/hooks/api";
|
||||||
|
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
|
||||||
|
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
popUp: UsePopUpState<["sshCa"]>;
|
||||||
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["sshCa"]>, state?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
friendlyName: z.string(),
|
||||||
|
keyAlgorithm: z.enum([
|
||||||
|
CertKeyAlgorithm.RSA_2048,
|
||||||
|
CertKeyAlgorithm.RSA_4096,
|
||||||
|
CertKeyAlgorithm.ECDSA_P256,
|
||||||
|
CertKeyAlgorithm.ECDSA_P384
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export const SshCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const projectId = currentWorkspace?.id || "";
|
||||||
|
const { data: ca } = useGetSshCaById((popUp?.sshCa?.data as { caId: string })?.caId || "");
|
||||||
|
|
||||||
|
const { mutateAsync: createMutateAsync } = useCreateSshCa();
|
||||||
|
const { mutateAsync: updateMutateAsync } = useUpdateSshCa();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
friendlyName: "",
|
||||||
|
keyAlgorithm: CertKeyAlgorithm.RSA_2048
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ca) {
|
||||||
|
reset({
|
||||||
|
friendlyName: ca.friendlyName,
|
||||||
|
keyAlgorithm: ca.keyAlgorithm
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
friendlyName: "",
|
||||||
|
keyAlgorithm: CertKeyAlgorithm.RSA_2048
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [ca]);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ friendlyName, keyAlgorithm }: FormData) => {
|
||||||
|
try {
|
||||||
|
if (!projectId) return;
|
||||||
|
|
||||||
|
if (ca) {
|
||||||
|
await updateMutateAsync({
|
||||||
|
caId: ca.id,
|
||||||
|
friendlyName
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { id: newCaId } = await createMutateAsync({
|
||||||
|
projectId,
|
||||||
|
friendlyName,
|
||||||
|
keyAlgorithm
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: `/${ProjectType.SSH}/$projectId/ca/$caId` as const,
|
||||||
|
params: {
|
||||||
|
projectId,
|
||||||
|
caId: newCaId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
handlePopUpToggle("sshCa", false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully ${ca ? "updated" : "created"} SSH CA`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to create SSH CA",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.sshCa?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
reset();
|
||||||
|
handlePopUpToggle("sshCa", isOpen);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent title={`${ca ? "View" : "Create"} SSH CA`}>
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
{ca && (
|
||||||
|
<FormControl label="CA ID">
|
||||||
|
<Input value={ca.id} isDisabled className="bg-white/[0.07]" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="friendlyName"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Friendly Name"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="My SSH CA" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="keyAlgorithm"
|
||||||
|
defaultValue={CertKeyAlgorithm.RSA_2048}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Key Algorithm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
isDisabled={Boolean(ca)}
|
||||||
|
>
|
||||||
|
{certKeyAlgorithms.map(({ label, value }) => (
|
||||||
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{popUp?.sshCa?.data ? "Update" : "Create"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => handlePopUpToggle("sshCa", false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
import { SshCaStatus, useDeleteSshCa, useUpdateSshCa } from "@app/hooks/api";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { SshCaModal } from "./SshCaModal";
|
||||||
|
import { SshCaTable } from "./SshCaTable";
|
||||||
|
|
||||||
|
export const SshCaSection = () => {
|
||||||
|
const { mutateAsync: deleteSshCa } = useDeleteSshCa();
|
||||||
|
const { mutateAsync: updateSshCa } = useUpdateSshCa();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"sshCa",
|
||||||
|
"deleteSshCa",
|
||||||
|
"sshCaStatus", // enable / disable
|
||||||
|
"upgradePlan"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const onRemoveSshCaSubmit = async (caId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteSshCa({ caId });
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted SSH CA",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("deleteSshCa");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete SSH CA",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUpdateSshCaStatus = async ({ caId, status }: { caId: string; status: SshCaStatus }) => {
|
||||||
|
try {
|
||||||
|
await updateSshCa({ caId, status });
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("sshCaStatus");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: `Failed to ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">Certificate Authorities</p>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Create}
|
||||||
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => handlePopUpOpen("sshCa")}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
>
|
||||||
|
Create SSH CA
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
<SshCaTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
<SshCaModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteSshCa.isOpen}
|
||||||
|
title="Are you sure want to remove the SSH CA?"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteSshCa", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onRemoveSshCaSubmit((popUp?.deleteSshCa?.data as { caId: string })?.caId)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.sshCaStatus.isOpen}
|
||||||
|
title={`Are you sure want to ${
|
||||||
|
(popUp?.sshCaStatus?.data as { status: string })?.status === SshCaStatus.ACTIVE
|
||||||
|
? "enable"
|
||||||
|
: "disable"
|
||||||
|
} the CA?`}
|
||||||
|
subTitle={
|
||||||
|
(popUp?.sshCaStatus?.data as { status: string })?.status === SshCaStatus.ACTIVE
|
||||||
|
? "This action will allow the SSH CA to start issuing certificates again."
|
||||||
|
: "This action will prevent the SSH CA from issuing new certificates."
|
||||||
|
}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("sshCaStatus", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onUpdateSshCaStatus(popUp?.sshCaStatus?.data as { caId: string; status: SshCaStatus })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* <UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import { faBan, faCertificate, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
EmptyState,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
|
import { SshCaStatus, useListWorkspaceSshCas } from "@app/hooks/api";
|
||||||
|
import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["deleteSshCa", "sshCaStatus"]>,
|
||||||
|
data?: object
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SshCaTable = ({ handlePopUpOpen }: Props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data, isPending } = useListWorkspaceSshCas(currentWorkspace?.id || "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Friendly Name</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
<Th />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isPending && <TableSkeleton columns={3} innerKey="org-ssh-cas" />}
|
||||||
|
{!isPending &&
|
||||||
|
data &&
|
||||||
|
data.length > 0 &&
|
||||||
|
data.map((ca) => {
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||||
|
key={`ca-${ca.id}`}
|
||||||
|
onClick={() =>
|
||||||
|
navigate({
|
||||||
|
to: `/${ProjectType.SSH}/$projectId/ca/$caId` as const,
|
||||||
|
params: {
|
||||||
|
projectId: currentWorkspace.id,
|
||||||
|
caId: ca.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Td>{ca.friendlyName}</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge variant={getCaStatusBadgeVariant(ca.status)}>
|
||||||
|
{caStatusToNameMap[ca.status]}
|
||||||
|
</Badge>
|
||||||
|
</Td>
|
||||||
|
<Td className="flex justify-end">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
|
<Tooltip content="More options">
|
||||||
|
<FontAwesomeIcon size="lg" icon={faEllipsis} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
{(ca.status === SshCaStatus.ACTIVE ||
|
||||||
|
ca.status === SshCaStatus.DISABLED) && (
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed &&
|
||||||
|
"pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("sshCaStatus", {
|
||||||
|
caId: ca.id,
|
||||||
|
status:
|
||||||
|
ca.status === SshCaStatus.ACTIVE
|
||||||
|
? SshCaStatus.DISABLED
|
||||||
|
: SshCaStatus.ACTIVE
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faBan} />}
|
||||||
|
>
|
||||||
|
{`${
|
||||||
|
ca.status === SshCaStatus.ACTIVE ? "Disable" : "Enable"
|
||||||
|
} SSH CA`}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
)}
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Delete}
|
||||||
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("deleteSshCa", {
|
||||||
|
caId: ca.id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||||
|
>
|
||||||
|
Delete SSH CA
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isPending && data?.length === 0 && (
|
||||||
|
<EmptyState
|
||||||
|
title="No SSH certificate authorities have been created"
|
||||||
|
icon={faCertificate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { SshCertificateModal } from "../../SshCaByIDPage/components/SshCertificateModal";
|
||||||
|
import { SshCertificatesTable } from "./SshCertificatesTable";
|
||||||
|
|
||||||
|
export const SshCertificatesSection = () => {
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["sshCertificate"] as const);
|
||||||
|
return (
|
||||||
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Create}
|
||||||
|
a={ProjectPermissionSub.SshCertificates}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => handlePopUpOpen("sshCertificate")}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
>
|
||||||
|
Request
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
<SshCertificatesTable />
|
||||||
|
<SshCertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { faCertificate } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
EmptyState,
|
||||||
|
Pagination,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import { useListWorkspaceSshCertificates } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { getSshCertStatusBadgeDetails } from "./SshCertificatesTable.utils";
|
||||||
|
|
||||||
|
const PER_PAGE_INIT = 25;
|
||||||
|
|
||||||
|
export const SshCertificatesTable = () => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||||
|
|
||||||
|
const { data, isPending } = useListWorkspaceSshCertificates({
|
||||||
|
projectId: currentWorkspace?.id || "",
|
||||||
|
offset: (page - 1) * perPage,
|
||||||
|
limit: perPage
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Principals</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
<Th>Not Before</Th>
|
||||||
|
<Th>Not After</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isPending && <TableSkeleton columns={4} innerKey="org-ssh-certificates" />}
|
||||||
|
{!isPending &&
|
||||||
|
data?.certificates?.map((certificate) => {
|
||||||
|
const { variant, label } = getSshCertStatusBadgeDetails(certificate.notAfter);
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
||||||
|
<Td>{certificate.principals.join(", ")}</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge variant={variant}>{label}</Badge>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{certificate.notBefore
|
||||||
|
? format(new Date(certificate.notBefore), "yyyy-MM-dd")
|
||||||
|
: "-"}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{certificate.notAfter
|
||||||
|
? format(new Date(certificate.notAfter), "yyyy-MM-dd")
|
||||||
|
: "-"}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isPending && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && (
|
||||||
|
<Pagination
|
||||||
|
count={data.totalCount}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={(newPage) => setPage(newPage)}
|
||||||
|
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isPending && !data?.certificates?.length && (
|
||||||
|
<EmptyState title="No SSH certificates have been issued" icon={faCertificate} />
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export const getSshCertStatusBadgeDetails = (notAfter: string) => {
|
||||||
|
const currentDate = new Date().getTime();
|
||||||
|
const notAfterDate = new Date(notAfter).getTime();
|
||||||
|
|
||||||
|
let variant: "success" | "primary" | "danger" = "success";
|
||||||
|
let label = "Active";
|
||||||
|
|
||||||
|
if (notAfterDate > currentDate) {
|
||||||
|
variant = "success";
|
||||||
|
label = "Active";
|
||||||
|
} else {
|
||||||
|
variant = "danger";
|
||||||
|
label = "Expired";
|
||||||
|
}
|
||||||
|
|
||||||
|
return { variant, label };
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { SshCaSection } from "./SshCaSection";
|
||||||
|
export { SshCertificatesSection } from "./SshCertificatesSection";
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { OverviewPage } from "./OverviewPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/overview"
|
||||||
|
)({
|
||||||
|
component: OverviewPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||||
|
|
||||||
|
const tabs = [{ name: "General", key: "tab-project-general", Component: ProjectGeneralTab }];
|
||||||
|
|
||||||
|
export const SettingsPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="w-full max-w-7xl px-6">
|
||||||
|
<div className="my-6">
|
||||||
|
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
|
||||||
|
</div>
|
||||||
|
<Tabs defaultValue={tabs[0].key}>
|
||||||
|
<TabList>
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<Tab value={tab.key} key={tab.key}>
|
||||||
|
{tab.name}
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</TabList>
|
||||||
|
{tabs.map(({ key, Component }) => (
|
||||||
|
<TabPanel value={key} key={key}>
|
||||||
|
<Component />
|
||||||
|
</TabPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
|
import { useProjectPermission, useSubscription, useWorkspace } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||||
|
import { useUpdateWorkspaceAuditLogsRetention } from "@app/hooks/api/workspace/queries";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
auditLogsRetentionDays: z.coerce.number().min(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const AuditLogsRetentionSection = () => {
|
||||||
|
const { mutateAsync: updateAuditLogsRetention } = useUpdateWorkspaceAuditLogsRetention();
|
||||||
|
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { membership } = useProjectPermission();
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting, isDirty },
|
||||||
|
handleSubmit
|
||||||
|
} = useForm<TForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
values: {
|
||||||
|
auditLogsRetentionDays:
|
||||||
|
currentWorkspace?.auditLogsRetentionDays ?? subscription?.auditLogsRetentionDays ?? 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentWorkspace) return null;
|
||||||
|
|
||||||
|
const handleAuditLogsRetentionSubmit = async ({ auditLogsRetentionDays }: TForm) => {
|
||||||
|
try {
|
||||||
|
if (!subscription?.auditLogs) {
|
||||||
|
handlePopUpOpen("upgradePlan", {
|
||||||
|
description: "You can only configure audit logs retention if you upgrade your plan."
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) {
|
||||||
|
handlePopUpOpen("upgradePlan", {
|
||||||
|
description:
|
||||||
|
"To update your audit logs retention period to a higher value, upgrade your plan."
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateAuditLogsRetention({
|
||||||
|
auditLogsRetentionDays,
|
||||||
|
projectSlug: currentWorkspace.slug
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully updated audit logs retention period",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed updating audit logs retention period",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// render only for dedicated/self-hosted instances of Infisical
|
||||||
|
if (
|
||||||
|
window.location.origin.includes("https://app.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://gamma.infisical.com")
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<p className="text-xl font-semibold">Audit Logs Retention</p>
|
||||||
|
</div>
|
||||||
|
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
||||||
|
Set the number of days to keep your project audit logs.
|
||||||
|
</p>
|
||||||
|
<form onSubmit={handleSubmit(handleAuditLogsRetentionSubmit)} autoComplete="off">
|
||||||
|
<div className="max-w-xs">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={0}
|
||||||
|
name="auditLogsRetentionDays"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Number of days"
|
||||||
|
>
|
||||||
|
<Input {...field} type="number" min={1} step={1} isDisabled={!isAdmin} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
disabled={!isAdmin || !isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AuditLogsRetentionSection } from "./AuditLogsRetentionSection";
|
||||||
+192
@@ -0,0 +1,192 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
|
import { LeaveProjectModal } from "@app/components/v2/LeaveProjectModal";
|
||||||
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useOrganization,
|
||||||
|
useProjectPermission,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { useDeleteWorkspace, useGetWorkspaceUsers, useLeaveProject } from "@app/hooks/api";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
export const DeleteProjectSection = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"deleteWorkspace",
|
||||||
|
"leaveWorkspace"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { hasProjectRole, membership } = useProjectPermission();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const [isDeleting, setIsDeleting] = useToggle();
|
||||||
|
const [isLeaving, setIsLeaving] = useToggle();
|
||||||
|
const deleteWorkspace = useDeleteWorkspace();
|
||||||
|
const leaveProject = useLeaveProject();
|
||||||
|
const { data: members, isPending: isMembersLoading } = useGetWorkspaceUsers(
|
||||||
|
currentWorkspace?.id || ""
|
||||||
|
);
|
||||||
|
|
||||||
|
// If isNoAccessMember is true, then the user can't read the workspace members. So we need to handle this case separately.
|
||||||
|
const isNoAccessMember = hasProjectRole("no-access");
|
||||||
|
|
||||||
|
const isOnlyAdminMember = useMemo(() => {
|
||||||
|
if (!members || !membership || !hasProjectRole("admin")) return false;
|
||||||
|
|
||||||
|
const adminMembers = members.filter(
|
||||||
|
(member) => member.roles.map((r) => r.role).includes("admin") && member.id !== membership.id // exclude the current user
|
||||||
|
);
|
||||||
|
|
||||||
|
return !adminMembers.length;
|
||||||
|
}, [members, membership]);
|
||||||
|
|
||||||
|
const handleDeleteWorkspaceSubmit = async () => {
|
||||||
|
setIsDeleting.on();
|
||||||
|
try {
|
||||||
|
if (!currentWorkspace?.id) return;
|
||||||
|
|
||||||
|
await deleteWorkspace.mutateAsync({
|
||||||
|
workspaceID: currentWorkspace?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted project",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||||
|
});
|
||||||
|
handlePopUpClose("deleteWorkspace");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete project",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsDeleting.off();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLeaveWorkspaceSubmit = async () => {
|
||||||
|
console.log({
|
||||||
|
currentWorkspace,
|
||||||
|
currentOrg,
|
||||||
|
members,
|
||||||
|
isNoAccessMember,
|
||||||
|
membership
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLeaving.on();
|
||||||
|
|
||||||
|
if (!currentWorkspace?.id || !currentOrg?.id) return;
|
||||||
|
|
||||||
|
// If there's no members, and the user has access to read members, something went wrong.
|
||||||
|
if (!members && !isNoAccessMember) return;
|
||||||
|
|
||||||
|
// If the user has elevated permissions and can read members:
|
||||||
|
if (!isNoAccessMember) {
|
||||||
|
if (!members) return;
|
||||||
|
|
||||||
|
if (members.length < 2) {
|
||||||
|
createNotification({
|
||||||
|
text: "You can't leave the project as you are the only member",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the user has access to read members, and there's less than 1 admin member excluding the current user, they can't leave the project.
|
||||||
|
if (isOnlyAdminMember) {
|
||||||
|
createNotification({
|
||||||
|
text: "You can't leave a project with no admin members left. Promote another member to admin first.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's actually a no-access member, then we don't really care about the members.
|
||||||
|
|
||||||
|
await leaveProject.mutateAsync({
|
||||||
|
workspaceId: currentWorkspace.id
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to leave project",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLeaving.off();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
|
||||||
|
<div className="space-x-4">
|
||||||
|
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Project}>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
isLoading={isDeleting}
|
||||||
|
isDisabled={!isAllowed || isDeleting}
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="outline_bg"
|
||||||
|
type="submit"
|
||||||
|
onClick={() => handlePopUpOpen("deleteWorkspace")}
|
||||||
|
>
|
||||||
|
{`Delete ${currentWorkspace?.name}`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
{!isOnlyAdminMember && (
|
||||||
|
<Button
|
||||||
|
disabled={isMembersLoading || (members && members?.length < 2)}
|
||||||
|
isLoading={isLeaving}
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="outline_bg"
|
||||||
|
type="submit"
|
||||||
|
onClick={() => handlePopUpOpen("leaveWorkspace")}
|
||||||
|
>
|
||||||
|
{`Leave ${currentWorkspace?.name}`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteWorkspace.isOpen}
|
||||||
|
title="Are you sure want to delete this project?"
|
||||||
|
subTitle={`Permanently delete ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteWorkspace", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
buttonText="Delete Project"
|
||||||
|
onDeleteApproved={handleDeleteWorkspaceSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LeaveProjectModal
|
||||||
|
isOpen={popUp.leaveWorkspace.isOpen}
|
||||||
|
title="Are you sure want to leave this project?"
|
||||||
|
subTitle={`If you leave ${currentWorkspace?.name} you will lose access to the project and it's contents.`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("leaveWorkspace", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
buttonText="Leave Project"
|
||||||
|
onLeaveApproved={handleLeaveWorkspaceSubmit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { DeleteProjectSection } from "./DeleteProjectSection";
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
import { AuditLogsRetentionSection } from "../AuditLogsRetentionSection";
|
||||||
|
import { DeleteProjectSection } from "../DeleteProjectSection";
|
||||||
|
import { ProjectOverviewChangeSection } from "../ProjectOverviewChangeSection";
|
||||||
|
|
||||||
|
export const ProjectGeneralTab = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ProjectOverviewChangeSection />
|
||||||
|
<AuditLogsRetentionSection />
|
||||||
|
<DeleteProjectSection />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ProjectGeneralTab } from "./ProjectGeneralTab";
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string;
|
||||||
|
hoverText: string;
|
||||||
|
notificationText: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CopyButton = ({ value, children, hoverText, notificationText }: Props) => {
|
||||||
|
const [isProjectIdCopied, setIsProjectIdCopied] = useToggle(false);
|
||||||
|
|
||||||
|
const copyToClipboard = useCallback(() => {
|
||||||
|
if (isProjectIdCopied) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsProjectIdCopied.on();
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: notificationText,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
const timer = setTimeout(() => setIsProjectIdCopied.off(), 2000);
|
||||||
|
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [isProjectIdCopied]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={isProjectIdCopied ? faCheck : faCopy} />}
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<span className="absolute -left-8 -top-20 hidden translate-y-full justify-center rounded-md bg-bunker-800 px-3 py-2 text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||||
|
{hoverText}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Button, FormControl, Input, TextArea } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
|
import { useUpdateProject } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { CopyButton } from "./CopyButton";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(256, "Description too long, max length is 256 characters")
|
||||||
|
.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const ProjectOverviewChangeSection = () => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { mutateAsync, isPending } = useUpdateProject();
|
||||||
|
|
||||||
|
const { handleSubmit, control, reset } = useForm<FormData>({ resolver: zodResolver(formSchema) });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentWorkspace) {
|
||||||
|
reset({
|
||||||
|
name: currentWorkspace.name,
|
||||||
|
description: currentWorkspace.description ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentWorkspace]);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ name, description }: FormData) => {
|
||||||
|
try {
|
||||||
|
if (!currentWorkspace?.id) return;
|
||||||
|
|
||||||
|
await mutateAsync({
|
||||||
|
projectID: currentWorkspace.id,
|
||||||
|
newProjectName: name,
|
||||||
|
newProjectDescription: description
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully updated project overview",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to update project overview",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="justify-betweens flex">
|
||||||
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Project Overview</h2>
|
||||||
|
<div className="space-x-2">
|
||||||
|
<CopyButton
|
||||||
|
value={currentWorkspace?.slug || ""}
|
||||||
|
hoverText="Click to project slug"
|
||||||
|
notificationText="Copied project slug to clipboard"
|
||||||
|
>
|
||||||
|
Copy Project Slug
|
||||||
|
</CopyButton>
|
||||||
|
<CopyButton
|
||||||
|
value={currentWorkspace?.id || ""}
|
||||||
|
hoverText="Click to project ID"
|
||||||
|
notificationText="Copied project ID to clipboard"
|
||||||
|
>
|
||||||
|
Copy Project ID
|
||||||
|
</CopyButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)} className="flex w-full flex-col gap-0">
|
||||||
|
<div className="flex w-full flex-row items-end gap-4">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.Project}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Project name"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Project name"
|
||||||
|
{...field}
|
||||||
|
className="bg-mineshaft-800"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="name"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full flex-row items-end gap-4">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.Project}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Project description"
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
placeholder="Project description"
|
||||||
|
{...field}
|
||||||
|
rows={3}
|
||||||
|
className="thin-scrollbar max-w-md !resize-none bg-mineshaft-800"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.Project}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isPending}
|
||||||
|
isDisabled={isPending || !isAllowed}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { ProjectOverviewChangeSection } from "./ProjectOverviewChangeSection";
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { DeleteProjectSection } from "./DeleteProjectSection";
|
||||||
|
export { ProjectOverviewChangeSection } from "./ProjectOverviewChangeSection";
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { SettingsPage } from "./SettingsPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings"
|
||||||
|
)({
|
||||||
|
component: SettingsPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
|
import { useDeleteSshCa, useGetSshCaById } from "@app/hooks/api";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { SshCaModal } from "../OverviewPage/components/SshCaModal";
|
||||||
|
import { SshCaDetailsSection, SshCertificateTemplatesSection } from "./components";
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const projectId = currentWorkspace?.id || "";
|
||||||
|
const caId = useParams({
|
||||||
|
from: ROUTE_PATHS.Ssh.SshCaByIDPage.id,
|
||||||
|
select: (el) => el.caId
|
||||||
|
});
|
||||||
|
const { data } = useGetSshCaById(caId);
|
||||||
|
|
||||||
|
const { mutateAsync: deleteSshCa } = useDeleteSshCa();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"sshCa",
|
||||||
|
"deleteSshCa"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const onRemoveCaSubmit = async (caIdToDelete: string) => {
|
||||||
|
try {
|
||||||
|
if (!projectId) return;
|
||||||
|
|
||||||
|
await deleteSshCa({ caId: caIdToDelete });
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted SSH CA",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("deleteSshCa");
|
||||||
|
navigate({
|
||||||
|
to: `/${ProjectType.SSH}/$projectId/overview` as const,
|
||||||
|
params: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete SSH CA",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
{data && (
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
|
onClick={() =>
|
||||||
|
navigate({
|
||||||
|
to: `/${ProjectType.SSH}/$projectId/overview`,
|
||||||
|
params: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
SSH Certificate Authorities
|
||||||
|
</Button>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<p className="text-3xl font-semibold text-white">{data.friendlyName}</p>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
|
<Tooltip content="More options">
|
||||||
|
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Delete}
|
||||||
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
isAllowed
|
||||||
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() =>
|
||||||
|
handlePopUpOpen("deleteSshCa", {
|
||||||
|
caId: data.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
>
|
||||||
|
Delete SSH CA
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="mr-4 w-96">
|
||||||
|
<SshCaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<SshCertificateTemplatesSection caId={caId} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<SshCaModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteSshCa.isOpen}
|
||||||
|
title="Are you sure want to remove the SSH CA from the project?"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteSshCa", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onRemoveCaSubmit((popUp?.deleteSshCa?.data as { caId: string })?.caId)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SshCaByIDPage = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>SSH Certificate Authority</title>
|
||||||
|
</Helmet>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Read}
|
||||||
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
|
passThrough={false}
|
||||||
|
renderGuardBanner
|
||||||
|
>
|
||||||
|
<Page />
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { faCheck, faCopy, faDownload, faPencil } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { IconButton, Tooltip } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
import { useTimedReset } from "@app/hooks";
|
||||||
|
import { useGetSshCaById } from "@app/hooks/api";
|
||||||
|
import { caStatusToNameMap } from "@app/hooks/api/ca/constants";
|
||||||
|
import { certKeyAlgorithmToNameMap } from "@app/hooks/api/certificates/constants";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
caId: string;
|
||||||
|
handlePopUpOpen: (popUpName: keyof UsePopUpState<["sshCa"]>, data?: object) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SshCaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {
|
||||||
|
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
||||||
|
initialState: "Copy ID to clipboard"
|
||||||
|
});
|
||||||
|
const [downloadText, isDownloading, setDownloadText] = useTimedReset<string>({
|
||||||
|
initialState: "Save public key"
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: ca } = useGetSshCaById(caId);
|
||||||
|
|
||||||
|
const downloadTxtFile = (filename: string, content: string) => {
|
||||||
|
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||||
|
FileSaver.saveAs(blob, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
return ca ? (
|
||||||
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">SSH CA Details</h3>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
|
>
|
||||||
|
{(isAllowed) => {
|
||||||
|
return (
|
||||||
|
<Tooltip content="Edit SSH CA">
|
||||||
|
<IconButton
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("sshCa", {
|
||||||
|
caId: ca.id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPencil} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
<div className="pt-4">
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">SSH CA ID</p>
|
||||||
|
<div className="group flex align-top">
|
||||||
|
<p className="text-sm text-mineshaft-300">{ca.id}</p>
|
||||||
|
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||||
|
<Tooltip content={copyTextId}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(ca.id);
|
||||||
|
setCopyTextId("Copied");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopyingId ? faCheck : faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Friendly Name</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">{ca.friendlyName}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Status</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">{caStatusToNameMap[ca.status]}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Key Algorithm</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">{certKeyAlgorithmToNameMap[ca.keyAlgorithm]}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Public Key</p>
|
||||||
|
<div className="group flex align-top">
|
||||||
|
<p className="flex-1 text-sm text-mineshaft-300">{ca.publicKey.substring(0, 20)}...</p>
|
||||||
|
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||||
|
<Tooltip content={downloadText}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
setDownloadText("Saved");
|
||||||
|
downloadTxtFile("ssh_ca.pub", ca.publicKey);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isDownloading ? faCheck : faDownload} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
|
import { IconButton, Tooltip } from "@app/components/v2";
|
||||||
|
import { useTimedReset } from "@app/hooks";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
serialNumber: string;
|
||||||
|
signedKey: string;
|
||||||
|
privateKey?: string;
|
||||||
|
publicKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SshCertificateContent = ({
|
||||||
|
serialNumber,
|
||||||
|
signedKey,
|
||||||
|
privateKey,
|
||||||
|
publicKey
|
||||||
|
}: Props) => {
|
||||||
|
const [copyTextSerialNumber, isCopyingSerialNumber, setCopyTextSerialNumber] =
|
||||||
|
useTimedReset<string>({
|
||||||
|
initialState: "Copy to clipboard"
|
||||||
|
});
|
||||||
|
const [copyTextCertificate, isCopyingCertificate, setCopyTextCertificate] = useTimedReset<string>(
|
||||||
|
{
|
||||||
|
initialState: "Copy to clipboard"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [copyTextCertificateSk, isCopyingCertificateSk, setCopyTextCertificateSk] =
|
||||||
|
useTimedReset<string>({
|
||||||
|
initialState: "Copy to clipboard"
|
||||||
|
});
|
||||||
|
|
||||||
|
const [copyTextCertificatePk, isCopyingCertificatePk, setCopyTextCertificatePk] =
|
||||||
|
useTimedReset<string>({
|
||||||
|
initialState: "Copy to clipboard"
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadTxtFile = (filename: string, content: string) => {
|
||||||
|
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||||
|
FileSaver.saveAs(blob, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-4">Serial Number</h2>
|
||||||
|
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
|
<p className="mr-4 break-all">{serialNumber}</p>
|
||||||
|
<Tooltip content={copyTextSerialNumber}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(serialNumber);
|
||||||
|
setCopyTextSerialNumber("Copied");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopyingSerialNumber ? faCheck : faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<h2>SSH Certificate / Signed Key</h2>
|
||||||
|
<div className="flex">
|
||||||
|
<Tooltip content={copyTextCertificate}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(signedKey);
|
||||||
|
setCopyTextCertificate("Copied");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopyingCertificate ? faCheck : faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Download">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
downloadTxtFile("user_key-cert.pub", signedKey);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
|
<p className="mr-4 whitespace-pre-wrap break-all">{signedKey}</p>
|
||||||
|
</div>
|
||||||
|
{privateKey && (
|
||||||
|
<>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<h2>Private Key</h2>
|
||||||
|
<div className="flex">
|
||||||
|
<Tooltip content={copyTextCertificateSk}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(privateKey);
|
||||||
|
setCopyTextCertificateSk("Copied");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopyingCertificateSk ? faCheck : faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content={copyTextCertificateSk}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
downloadTxtFile("user_key.pem", privateKey);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
|
<p className="mr-4 whitespace-pre-wrap break-all">{privateKey}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{publicKey && (
|
||||||
|
<>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<h2>Public Key</h2>
|
||||||
|
<div className="flex">
|
||||||
|
<Tooltip content={copyTextCertificatePk}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(publicKey);
|
||||||
|
setCopyTextCertificatePk("Copied");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopyingCertificatePk ? faCheck : faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content={copyTextCertificatePk}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
downloadTxtFile("user_key.pub", publicKey);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
|
<p className="mr-4 whitespace-pre-wrap break-all">{publicKey}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import {
|
||||||
|
SshCertTemplateStatus,
|
||||||
|
useGetSshCertTemplate,
|
||||||
|
useIssueSshCreds,
|
||||||
|
useListWorkspaceSshCertificateTemplates,
|
||||||
|
useSignSshKey
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
|
||||||
|
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
|
||||||
|
import { SshCertType } from "@app/hooks/api/sshCa/constants";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { SshCertificateContent } from "./SshCertificateContent";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
templateId: z.string(),
|
||||||
|
publicKey: z.string().optional(),
|
||||||
|
keyAlgorithm: z.enum([
|
||||||
|
CertKeyAlgorithm.RSA_2048,
|
||||||
|
CertKeyAlgorithm.RSA_4096,
|
||||||
|
CertKeyAlgorithm.ECDSA_P256,
|
||||||
|
CertKeyAlgorithm.ECDSA_P384
|
||||||
|
]),
|
||||||
|
certType: z.nativeEnum(SshCertType),
|
||||||
|
principals: z.string(),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a valid time string such as 2 days, 1d, 2h 1y, ...")
|
||||||
|
.optional(),
|
||||||
|
keyId: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
popUp: UsePopUpState<["sshCertificate"]>;
|
||||||
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["sshCertificate"]>, state?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TSshCertificateDetails = {
|
||||||
|
serialNumber: string;
|
||||||
|
signedKey: string;
|
||||||
|
privateKey?: string;
|
||||||
|
publicKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SshCertificateOperation {
|
||||||
|
SIGN_SSH_KEY = "sign-ssh-key",
|
||||||
|
ISSUE_SSH_CREDS = "issue-ssh-creds"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const projectId = currentWorkspace?.id || "";
|
||||||
|
const [operation, setOperation] = useState<SshCertificateOperation>(
|
||||||
|
SshCertificateOperation.SIGN_SSH_KEY
|
||||||
|
);
|
||||||
|
const [certificateDetails, setCertificateDetails] = useState<TSshCertificateDetails | null>(null);
|
||||||
|
|
||||||
|
const { mutateAsync: signSshKey } = useSignSshKey();
|
||||||
|
const { mutateAsync: issueSshCreds } = useIssueSshCreds();
|
||||||
|
|
||||||
|
const popUpData = popUp?.sshCertificate?.data as {
|
||||||
|
sshCaId: string;
|
||||||
|
templateId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: templatesData } = useListWorkspaceSshCertificateTemplates(projectId);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
setValue,
|
||||||
|
watch
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
keyAlgorithm: CertKeyAlgorithm.RSA_2048,
|
||||||
|
certType: SshCertType.USER
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const templateId = watch("templateId");
|
||||||
|
const { data: templateData } = useGetSshCertTemplate(templateId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (popUpData) {
|
||||||
|
setValue("templateId", popUpData.templateId);
|
||||||
|
} else if (templatesData && templatesData.certificateTemplates.length > 0) {
|
||||||
|
setValue("templateId", templatesData.certificateTemplates[0].id);
|
||||||
|
}
|
||||||
|
}, [popUpData]);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({
|
||||||
|
keyAlgorithm,
|
||||||
|
certType,
|
||||||
|
publicKey: existingPublicKey,
|
||||||
|
principals,
|
||||||
|
ttl,
|
||||||
|
keyId
|
||||||
|
}: FormData) => {
|
||||||
|
try {
|
||||||
|
if (!templateData) return;
|
||||||
|
if (!projectId) return;
|
||||||
|
|
||||||
|
switch (operation) {
|
||||||
|
case SshCertificateOperation.SIGN_SSH_KEY: {
|
||||||
|
const { serialNumber, signedKey } = await signSshKey({
|
||||||
|
projectId,
|
||||||
|
certificateTemplateId: templateData.id,
|
||||||
|
publicKey: existingPublicKey,
|
||||||
|
certType,
|
||||||
|
principals: principals.split(",").map((user) => user.trim()),
|
||||||
|
ttl,
|
||||||
|
keyId
|
||||||
|
});
|
||||||
|
|
||||||
|
setCertificateDetails({
|
||||||
|
serialNumber,
|
||||||
|
signedKey
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SshCertificateOperation.ISSUE_SSH_CREDS: {
|
||||||
|
const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({
|
||||||
|
projectId,
|
||||||
|
certificateTemplateId: templateData.id,
|
||||||
|
keyAlgorithm,
|
||||||
|
certType,
|
||||||
|
principals: principals.split(",").map((user) => user.trim()),
|
||||||
|
ttl,
|
||||||
|
keyId
|
||||||
|
});
|
||||||
|
|
||||||
|
setCertificateDetails({
|
||||||
|
serialNumber,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
signedKey
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully created SSH certificate",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to create SSH certificate",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.sshCertificate?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
handlePopUpToggle("sshCertificate", isOpen);
|
||||||
|
reset();
|
||||||
|
setCertificateDetails(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent title="Issue SSH Certificate">
|
||||||
|
{!certificateDetails ? (
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="templateId"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Certificate Template"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
isDisabled={Boolean(popUpData?.sshCaId)}
|
||||||
|
>
|
||||||
|
{(templatesData?.certificateTemplates || [])
|
||||||
|
.filter((template) => template.status === SshCertTemplateStatus.ACTIVE)
|
||||||
|
.map(({ id, name }) => (
|
||||||
|
<SelectItem value={id} key={`ssh-cert-template-${id}`}>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormControl label="Operation">
|
||||||
|
<Select
|
||||||
|
defaultValue="issue-ssh-creds"
|
||||||
|
value={operation}
|
||||||
|
onValueChange={(e) => setOperation(e as SshCertificateOperation)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectItem value="sign-ssh-key" key="sign-ssh-key">
|
||||||
|
Sign SSH Key
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="issue-ssh-creds" key="issue-ssh-creds">
|
||||||
|
Issue SSH Credentials
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="certType"
|
||||||
|
defaultValue={SshCertType.USER}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Certificate Type"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{templateData && templateData.allowUserCertificates && (
|
||||||
|
<SelectItem value={SshCertType.USER}>User</SelectItem>
|
||||||
|
)}
|
||||||
|
{templateData && templateData.allowHostCertificates && (
|
||||||
|
<SelectItem value={SshCertType.HOST}>Host</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{operation === SshCertificateOperation.SIGN_SSH_KEY && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="publicKey"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="SSH Public Key"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="ssh-rsa AAA ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{operation === SshCertificateOperation.ISSUE_SSH_CREDS && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="keyAlgorithm"
|
||||||
|
defaultValue={CertKeyAlgorithm.RSA_2048}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Key Algorithm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{certKeyAlgorithms.map(({ label, value }) => (
|
||||||
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="principals"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Principal(s)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="ec2-user" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="ttl"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="TTL" isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{templateData && templateData.allowCustomKeyIds && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="keyId"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Key ID" isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input {...field} placeholder="12345678" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="mt-4 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => handlePopUpToggle("sshCertificate", false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<SshCertificateContent
|
||||||
|
serialNumber={certificateDetails.serialNumber}
|
||||||
|
signedKey={certificateDetails.signedKey}
|
||||||
|
publicKey={certificateDetails.publicKey}
|
||||||
|
privateKey={certificateDetails.privateKey}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
Switch
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import {
|
||||||
|
useCreateSshCertTemplate,
|
||||||
|
useGetSshCaById,
|
||||||
|
useGetSshCertTemplate,
|
||||||
|
useListWorkspaceSshCas,
|
||||||
|
useUpdateSshCertTemplate
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
// Validates usernames or wildcard (*)
|
||||||
|
export const isValidUserPattern = (value: string): boolean => {
|
||||||
|
// Matches valid Linux usernames or a wildcard (*)
|
||||||
|
const userRegex = /^(?:\*|[a-z_][a-z0-9_-]{0,31})$/;
|
||||||
|
return userRegex.test(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validates hostnames, wildcard domains, or IP addresses
|
||||||
|
export const isValidHostPattern = (value: string): boolean => {
|
||||||
|
// Matches FQDNs, wildcard domains (*.example.com), IPv4, and IPv6 addresses
|
||||||
|
const hostRegex =
|
||||||
|
/^(?:\*|\*\.[a-z0-9-]+(?:\.[a-z0-9-]+)*|[a-z0-9-]+(?:\.[a-z0-9-]+)*|\d{1,3}(\.\d{1,3}){3}|([a-fA-F0-9:]+:+)+[a-fA-F0-9]+(?:%[a-zA-Z0-9]+)?)$/;
|
||||||
|
return hostRegex.test(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
sshCaId: z.string(),
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.min(1)
|
||||||
|
.max(36)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Invalid name. Name can only contain alphanumeric characters and hyphens."
|
||||||
|
}),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine(
|
||||||
|
(val) => ms(val) > 0,
|
||||||
|
"TTL must be a valid time string such as 2 days, 1d, 2h 1y, ..."
|
||||||
|
)
|
||||||
|
.default("1h"),
|
||||||
|
maxTTL: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine(
|
||||||
|
(val) => ms(val) > 0,
|
||||||
|
"Max TTL must be a valid time string such as 2 days, 1d, 2h 1y, ..."
|
||||||
|
)
|
||||||
|
.default("30d"),
|
||||||
|
allowedUsers: z.string().refine(
|
||||||
|
(val) => {
|
||||||
|
const trimmed = val.trim();
|
||||||
|
if (trimmed === "") return true;
|
||||||
|
const users = trimmed.split(",").map((u) => u.trim());
|
||||||
|
return users.every(isValidUserPattern);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Invalid user pattern in allowedUsers"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
allowedHosts: z.string().refine(
|
||||||
|
(val) => {
|
||||||
|
const trimmed = val.trim();
|
||||||
|
if (trimmed === "") return true;
|
||||||
|
const users = trimmed.split(",").map((u) => u.trim());
|
||||||
|
return users.every(isValidHostPattern);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Invalid host pattern in allowedHosts"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
allowUserCertificates: z.boolean().optional().default(false),
|
||||||
|
allowHostCertificates: z.boolean().optional().default(false),
|
||||||
|
allowCustomKeyIds: z.boolean().optional().default(false)
|
||||||
|
})
|
||||||
|
.refine((data) => ms(data.maxTTL) > ms(data.ttl), {
|
||||||
|
message: "Max TLL must be greater than TTL",
|
||||||
|
path: ["maxTTL"]
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
sshCaId: string;
|
||||||
|
popUp: UsePopUpState<["sshCertificateTemplate"]>;
|
||||||
|
handlePopUpToggle: (
|
||||||
|
popUpName: keyof UsePopUpState<["sshCertificateTemplate"]>,
|
||||||
|
state?: boolean
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SshCertificateTemplateModal = ({ popUp, handlePopUpToggle, sshCaId }: Props) => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
|
const { data: ca } = useGetSshCaById(sshCaId);
|
||||||
|
|
||||||
|
const { data: certTemplate } = useGetSshCertTemplate(
|
||||||
|
(popUp?.sshCertificateTemplate?.data as { id: string })?.id || ""
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: cas } = useListWorkspaceSshCas(currentWorkspace?.id || "");
|
||||||
|
|
||||||
|
const { mutateAsync: createSshCertTemplate } = useCreateSshCertTemplate();
|
||||||
|
const { mutateAsync: updateSshCertTemplate } = useUpdateSshCertTemplate();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (certTemplate) {
|
||||||
|
reset({
|
||||||
|
sshCaId: certTemplate.sshCaId,
|
||||||
|
name: certTemplate.name,
|
||||||
|
ttl: certTemplate.ttl,
|
||||||
|
maxTTL: certTemplate.maxTTL,
|
||||||
|
allowedUsers: certTemplate.allowedUsers.join(", "),
|
||||||
|
allowedHosts: certTemplate.allowedHosts.join(", "),
|
||||||
|
allowUserCertificates: certTemplate.allowUserCertificates,
|
||||||
|
allowHostCertificates: certTemplate.allowHostCertificates,
|
||||||
|
allowCustomKeyIds: certTemplate.allowCustomKeyIds
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
sshCaId,
|
||||||
|
name: "",
|
||||||
|
ttl: "1h",
|
||||||
|
maxTTL: "30d",
|
||||||
|
allowedUsers: "",
|
||||||
|
allowedHosts: "",
|
||||||
|
allowUserCertificates: false,
|
||||||
|
allowHostCertificates: false,
|
||||||
|
allowCustomKeyIds: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [certTemplate, ca]);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({
|
||||||
|
name,
|
||||||
|
ttl,
|
||||||
|
maxTTL,
|
||||||
|
allowUserCertificates,
|
||||||
|
allowHostCertificates,
|
||||||
|
allowedUsers,
|
||||||
|
allowedHosts,
|
||||||
|
allowCustomKeyIds
|
||||||
|
}: FormData) => {
|
||||||
|
try {
|
||||||
|
if (certTemplate) {
|
||||||
|
await updateSshCertTemplate({
|
||||||
|
id: certTemplate.id,
|
||||||
|
name,
|
||||||
|
ttl,
|
||||||
|
maxTTL,
|
||||||
|
allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [],
|
||||||
|
allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [],
|
||||||
|
allowUserCertificates,
|
||||||
|
allowHostCertificates,
|
||||||
|
allowCustomKeyIds
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully updated SSH certificate template",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createSshCertTemplate({
|
||||||
|
sshCaId,
|
||||||
|
name,
|
||||||
|
ttl,
|
||||||
|
maxTTL,
|
||||||
|
allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [],
|
||||||
|
allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [],
|
||||||
|
allowUserCertificates,
|
||||||
|
allowHostCertificates,
|
||||||
|
allowCustomKeyIds
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully created SSH certificate template",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
handlePopUpToggle("sshCertificateTemplate", false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to save changes",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.sshCertificateTemplate?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
handlePopUpToggle("sshCertificateTemplate", isOpen);
|
||||||
|
reset();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title={certTemplate ? "SSH Certificate Template" : "Create SSH Certificate Template"}
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
{certTemplate && (
|
||||||
|
<FormControl label="SSH Certificate Template ID">
|
||||||
|
<Input value={certTemplate.id} isDisabled className="bg-white/[0.07]" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="SSH Template Name"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="administrator" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="sshCaId"
|
||||||
|
defaultValue={sshCaId}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Issuing SSH CA"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="mt-4"
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
isDisabled
|
||||||
|
>
|
||||||
|
{(cas || []).map(({ id, friendlyName }) => (
|
||||||
|
<SelectItem value={id} key={`ssh-ca-${id}`}>
|
||||||
|
{friendlyName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="allowedUsers"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Allowed Users"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="ec2-user, developer, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="allowedHosts"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Allowed Hosts"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="*.compute.amazonaws.com, api.example.com, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="ttl"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Default TTL"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="maxTTL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Max TTL"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="allowUserCertificates"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="allow-user-certificates"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="ml-1 w-full">Allow User Certificates</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="allowHostCertificates"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="allow-host-certificates"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="ml-1 w-full">Allow Host Certificates</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="allowCustomKeyIds"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="allow-custom-key-ids"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="ml-1 w-full">Allow Custom Key IDs</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="mt-4 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => handlePopUpToggle("sshCertificateTemplate", false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { DeleteActionModal, IconButton } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import {
|
||||||
|
SshCertTemplateStatus,
|
||||||
|
useDeleteSshCertTemplate,
|
||||||
|
useUpdateSshCertTemplate
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { SshCertificateModal } from "./SshCertificateModal";
|
||||||
|
import { SshCertificateTemplateModal } from "./SshCertificateTemplateModal";
|
||||||
|
import { SshCertificateTemplatesTable } from "./SshCertificateTemplatesTable";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
caId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SshCertificateTemplatesSection = ({ caId }: Props) => {
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"sshCertificateTemplate",
|
||||||
|
"sshCertificateTemplateStatus",
|
||||||
|
"sshCertificate",
|
||||||
|
"deleteSshCertificateTemplate",
|
||||||
|
"upgradePlan"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const { mutateAsync: deleteSshCertTemplate } = useDeleteSshCertTemplate();
|
||||||
|
const { mutateAsync: updateSshCertTemplate } = useUpdateSshCertTemplate();
|
||||||
|
|
||||||
|
const onRemoveSshCertificateTemplateSubmit = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await deleteSshCertTemplate({
|
||||||
|
id
|
||||||
|
});
|
||||||
|
|
||||||
|
await createNotification({
|
||||||
|
text: "Successfully deleted SSH certificate template",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("deleteSshCertificateTemplate");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete SSH certificate template",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUpdateSshCaStatus = async ({
|
||||||
|
templateId,
|
||||||
|
status
|
||||||
|
}: {
|
||||||
|
templateId: string;
|
||||||
|
status: SshCertTemplateStatus;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
await updateSshCertTemplate({ id: templateId, status });
|
||||||
|
|
||||||
|
await createNotification({
|
||||||
|
text: `Successfully ${
|
||||||
|
status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled"
|
||||||
|
} SSH certificate template`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("sshCertificateTemplateStatus");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: `Failed to ${
|
||||||
|
status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled"
|
||||||
|
} SSH certificate template`,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Certificate Templates</h3>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Create}
|
||||||
|
a={ProjectPermissionSub.SshCertificateTemplates}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => handlePopUpOpen("sshCertificateTemplate")}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPlus} />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
<div className="py-4">
|
||||||
|
<SshCertificateTemplatesTable handlePopUpOpen={handlePopUpOpen} sshCaId={caId} />
|
||||||
|
</div>
|
||||||
|
<SshCertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
<SshCertificateTemplateModal
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
sshCaId={caId}
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteSshCertificateTemplate.isOpen}
|
||||||
|
title={`Are you sure want to delete the SSH certificate template ${
|
||||||
|
(popUp?.deleteSshCertificateTemplate?.data as { name: string })?.name || ""
|
||||||
|
}?`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteSshCertificateTemplate", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onRemoveSshCertificateTemplateSubmit(
|
||||||
|
(popUp?.deleteSshCertificateTemplate?.data as { id: string })?.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.sshCertificateTemplateStatus.isOpen}
|
||||||
|
title={`Are you sure want to ${
|
||||||
|
(popUp?.sshCertificateTemplateStatus?.data as { status: string })?.status ===
|
||||||
|
SshCertTemplateStatus.ACTIVE
|
||||||
|
? "enable"
|
||||||
|
: "disable"
|
||||||
|
} this certificate template?`}
|
||||||
|
subTitle={
|
||||||
|
(popUp?.sshCertificateTemplateStatus?.data as { status: string })?.status ===
|
||||||
|
SshCertTemplateStatus.ACTIVE
|
||||||
|
? "This action will allow certificate issuance under this template again."
|
||||||
|
: "This action will prevent certificate issuance under this template."
|
||||||
|
}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("sshCertificateTemplateStatus", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onUpdateSshCaStatus(
|
||||||
|
popUp?.sshCertificateTemplateStatus?.data as {
|
||||||
|
templateId: string;
|
||||||
|
status: SshCertTemplateStatus;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
buttonText={
|
||||||
|
(popUp?.sshCertificateTemplateStatus?.data as { status: string })?.status ===
|
||||||
|
SshCertTemplateStatus.ACTIVE
|
||||||
|
? "Enable"
|
||||||
|
: "Disable"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import {
|
||||||
|
faBan,
|
||||||
|
faCertificate,
|
||||||
|
faEllipsis,
|
||||||
|
faFileAlt,
|
||||||
|
faTrash
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
EmptyState,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
import { SshCertTemplateStatus, useGetSshCaCertTemplates } from "@app/hooks/api";
|
||||||
|
import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
sshCaId: string;
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<
|
||||||
|
[
|
||||||
|
"sshCertificateTemplate",
|
||||||
|
"sshCertificateTemplateStatus",
|
||||||
|
"sshCertificate",
|
||||||
|
"deleteSshCertificateTemplate",
|
||||||
|
"upgradePlan"
|
||||||
|
]
|
||||||
|
>,
|
||||||
|
data?: {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
sshCaId?: string;
|
||||||
|
status?: SshCertTemplateStatus;
|
||||||
|
templateId?: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SshCertificateTemplatesTable = ({ handlePopUpOpen, sshCaId }: Props) => {
|
||||||
|
const { data, isPending } = useGetSshCaCertTemplates(sshCaId);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Name</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
<Th />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isPending && <TableSkeleton columns={2} innerKey="project-cas" />}
|
||||||
|
{!isPending &&
|
||||||
|
data?.certificateTemplates.map((certificateTemplate) => {
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`certificate-${certificateTemplate.id}`}>
|
||||||
|
<Td>{certificateTemplate.name}</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge variant={getCaStatusBadgeVariant(certificateTemplate.status)}>
|
||||||
|
{caStatusToNameMap[certificateTemplate.status]}
|
||||||
|
</Badge>
|
||||||
|
</Td>
|
||||||
|
<Td className="flex justify-end">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
|
<Tooltip content="More options">
|
||||||
|
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.SshCertificateTemplates}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("sshCertificateTemplateStatus", {
|
||||||
|
templateId: certificateTemplate.id,
|
||||||
|
status:
|
||||||
|
certificateTemplate.status === SshCertTemplateStatus.ACTIVE
|
||||||
|
? SshCertTemplateStatus.DISABLED
|
||||||
|
: SshCertTemplateStatus.ACTIVE
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faBan} />}
|
||||||
|
>
|
||||||
|
{`${
|
||||||
|
certificateTemplate.status === SshCertTemplateStatus.ACTIVE
|
||||||
|
? "Disable"
|
||||||
|
: "Enable"
|
||||||
|
} Template`}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.SshCertificateTemplates}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("sshCertificate", {
|
||||||
|
sshCaId,
|
||||||
|
templateId: certificateTemplate.id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
icon={
|
||||||
|
<FontAwesomeIcon icon={faCertificate} size="sm" className="mr-1" />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Issue Certificate
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.SshCertificateTemplates}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
handlePopUpOpen("sshCertificateTemplate", {
|
||||||
|
id: certificateTemplate.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
icon={<FontAwesomeIcon icon={faFileAlt} size="sm" className="mr-1" />}
|
||||||
|
>
|
||||||
|
Edit Template
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Delete}
|
||||||
|
a={ProjectPermissionSub.SshCertificateTemplates}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faTrash} size="sm" className="mr-1" />}
|
||||||
|
onClick={() =>
|
||||||
|
handlePopUpOpen("deleteSshCertificateTemplate", {
|
||||||
|
id: certificateTemplate.id,
|
||||||
|
name: certificateTemplate.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Delete Template
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isPending && !data?.certificateTemplates?.length && (
|
||||||
|
<EmptyState
|
||||||
|
title="No certificate templates have been created for this SSH CA"
|
||||||
|
icon={faFileAlt}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { SshCaDetailsSection } from "./SshCaDetailsSection";
|
||||||
|
export { SshCertificateModal } from "./SshCertificateModal";
|
||||||
|
export { SshCertificateTemplatesSection } from "./SshCertificateTemplatesSection";
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { SshCaByIDPage } from "./SshCaByIDPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId"
|
||||||
|
)({
|
||||||
|
component: SshCaByIDPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
)({
|
||||||
|
component: ProjectLayout,
|
||||||
|
beforeLoad: async ({ params, context }) => {
|
||||||
|
await context.queryClient.ensureQueryData({
|
||||||
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
|
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.queryClient.ensureQueryData({
|
||||||
|
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||||
|
workspaceId: params.projectId
|
||||||
|
}),
|
||||||
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -39,6 +39,7 @@ import { Route as authProviderSuccessPageRouteImport } from './pages/auth/Provid
|
|||||||
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
|
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
|
||||||
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
|
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
|
||||||
import { Route as adminOverviewPageRouteImport } from './pages/admin/OverviewPage/route'
|
import { Route as adminOverviewPageRouteImport } from './pages/admin/OverviewPage/route'
|
||||||
|
import { Route as sshLayoutImport } from './pages/ssh/layout'
|
||||||
import { Route as secretManagerLayoutImport } from './pages/secret-manager/layout'
|
import { Route as secretManagerLayoutImport } from './pages/secret-manager/layout'
|
||||||
import { Route as kmsLayoutImport } from './pages/kms/layout'
|
import { Route as kmsLayoutImport } from './pages/kms/layout'
|
||||||
import { Route as certManagerLayoutImport } from './pages/cert-manager/layout'
|
import { Route as certManagerLayoutImport } from './pages/cert-manager/layout'
|
||||||
@@ -50,14 +51,18 @@ import { Route as organizationBillingPageRouteImport } from './pages/organizatio
|
|||||||
import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route'
|
import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route'
|
||||||
import { Route as organizationAdminPageRouteImport } from './pages/organization/AdminPage/route'
|
import { Route as organizationAdminPageRouteImport } from './pages/organization/AdminPage/route'
|
||||||
import { Route as organizationAccessManagementPageRouteImport } from './pages/organization/AccessManagementPage/route'
|
import { Route as organizationAccessManagementPageRouteImport } from './pages/organization/AccessManagementPage/route'
|
||||||
|
import { Route as projectAccessControlPageRouteSshImport } from './pages/project/AccessControlPage/route-ssh'
|
||||||
import { Route as projectAccessControlPageRouteSecretManagerImport } from './pages/project/AccessControlPage/route-secret-manager'
|
import { Route as projectAccessControlPageRouteSecretManagerImport } from './pages/project/AccessControlPage/route-secret-manager'
|
||||||
import { Route as projectAccessControlPageRouteKmsImport } from './pages/project/AccessControlPage/route-kms'
|
import { Route as projectAccessControlPageRouteKmsImport } from './pages/project/AccessControlPage/route-kms'
|
||||||
import { Route as projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager'
|
import { Route as projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager'
|
||||||
|
import { Route as sshSettingsPageRouteImport } from './pages/ssh/SettingsPage/route'
|
||||||
|
import { Route as sshOverviewPageRouteImport } from './pages/ssh/OverviewPage/route'
|
||||||
import { Route as secretManagerSettingsPageRouteImport } from './pages/secret-manager/SettingsPage/route'
|
import { Route as secretManagerSettingsPageRouteImport } from './pages/secret-manager/SettingsPage/route'
|
||||||
import { Route as secretManagerSecretRotationPageRouteImport } from './pages/secret-manager/SecretRotationPage/route'
|
import { Route as secretManagerSecretRotationPageRouteImport } from './pages/secret-manager/SecretRotationPage/route'
|
||||||
import { Route as secretManagerOverviewPageRouteImport } from './pages/secret-manager/OverviewPage/route'
|
import { Route as secretManagerOverviewPageRouteImport } from './pages/secret-manager/OverviewPage/route'
|
||||||
import { Route as secretManagerSecretApprovalsPageRouteImport } from './pages/secret-manager/SecretApprovalsPage/route'
|
import { Route as secretManagerSecretApprovalsPageRouteImport } from './pages/secret-manager/SecretApprovalsPage/route'
|
||||||
import { Route as secretManagerIPAllowlistPageRouteImport } from './pages/secret-manager/IPAllowlistPage/route'
|
import { Route as secretManagerIPAllowlistPageRouteImport } from './pages/secret-manager/IPAllowlistPage/route'
|
||||||
|
import { Route as organizationSshOverviewPageRouteImport } from './pages/organization/SshOverviewPage/route'
|
||||||
import { Route as organizationSecretManagerOverviewPageRouteImport } from './pages/organization/SecretManagerOverviewPage/route'
|
import { Route as organizationSecretManagerOverviewPageRouteImport } from './pages/organization/SecretManagerOverviewPage/route'
|
||||||
import { Route as organizationRoleByIDPageRouteImport } from './pages/organization/RoleByIDPage/route'
|
import { Route as organizationRoleByIDPageRouteImport } from './pages/organization/RoleByIDPage/route'
|
||||||
import { Route as organizationUserDetailsByIDPageRouteImport } from './pages/organization/UserDetailsByIDPage/route'
|
import { Route as organizationUserDetailsByIDPageRouteImport } from './pages/organization/UserDetailsByIDPage/route'
|
||||||
@@ -69,6 +74,9 @@ import { Route as kmsSettingsPageRouteImport } from './pages/kms/SettingsPage/ro
|
|||||||
import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/route'
|
import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/route'
|
||||||
import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route'
|
import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route'
|
||||||
import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route'
|
import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route'
|
||||||
|
import { Route as projectRoleDetailsBySlugPageRouteSshImport } from './pages/project/RoleDetailsBySlugPage/route-ssh'
|
||||||
|
import { Route as projectMemberDetailsByIDPageRouteSshImport } from './pages/project/MemberDetailsByIDPage/route-ssh'
|
||||||
|
import { Route as projectIdentityDetailsByIDPageRouteSshImport } from './pages/project/IdentityDetailsByIDPage/route-ssh'
|
||||||
import { Route as projectRoleDetailsBySlugPageRouteSecretManagerImport } from './pages/project/RoleDetailsBySlugPage/route-secret-manager'
|
import { Route as projectRoleDetailsBySlugPageRouteSecretManagerImport } from './pages/project/RoleDetailsBySlugPage/route-secret-manager'
|
||||||
import { Route as projectMemberDetailsByIDPageRouteSecretManagerImport } from './pages/project/MemberDetailsByIDPage/route-secret-manager'
|
import { Route as projectMemberDetailsByIDPageRouteSecretManagerImport } from './pages/project/MemberDetailsByIDPage/route-secret-manager'
|
||||||
import { Route as projectIdentityDetailsByIDPageRouteSecretManagerImport } from './pages/project/IdentityDetailsByIDPage/route-secret-manager'
|
import { Route as projectIdentityDetailsByIDPageRouteSecretManagerImport } from './pages/project/IdentityDetailsByIDPage/route-secret-manager'
|
||||||
@@ -79,6 +87,7 @@ import { Route as projectRoleDetailsBySlugPageRouteCertManagerImport } from './p
|
|||||||
import { Route as certManagerPkiCollectionDetailsByIDPageRoutesImport } from './pages/cert-manager/PkiCollectionDetailsByIDPage/routes'
|
import { Route as certManagerPkiCollectionDetailsByIDPageRoutesImport } from './pages/cert-manager/PkiCollectionDetailsByIDPage/routes'
|
||||||
import { Route as projectMemberDetailsByIDPageRouteCertManagerImport } from './pages/project/MemberDetailsByIDPage/route-cert-manager'
|
import { Route as projectMemberDetailsByIDPageRouteCertManagerImport } from './pages/project/MemberDetailsByIDPage/route-cert-manager'
|
||||||
import { Route as projectIdentityDetailsByIDPageRouteCertManagerImport } from './pages/project/IdentityDetailsByIDPage/route-cert-manager'
|
import { Route as projectIdentityDetailsByIDPageRouteCertManagerImport } from './pages/project/IdentityDetailsByIDPage/route-cert-manager'
|
||||||
|
import { Route as sshSshCaByIDPageRouteImport } from './pages/ssh/SshCaByIDPage/route'
|
||||||
import { Route as secretManagerSecretDashboardPageRouteImport } from './pages/secret-manager/SecretDashboardPage/route'
|
import { Route as secretManagerSecretDashboardPageRouteImport } from './pages/secret-manager/SecretDashboardPage/route'
|
||||||
import { Route as certManagerCertAuthDetailsByIDPageRouteImport } from './pages/cert-manager/CertAuthDetailsByIDPage/route'
|
import { Route as certManagerCertAuthDetailsByIDPageRouteImport } from './pages/cert-manager/CertAuthDetailsByIDPage/route'
|
||||||
import { Route as organizationAppConnectionsGithubOauthCallbackPageRouteImport } from './pages/organization/AppConnections/GithubOauthCallbackPage/route'
|
import { Route as organizationAppConnectionsGithubOauthCallbackPageRouteImport } from './pages/organization/AppConnections/GithubOauthCallbackPage/route'
|
||||||
@@ -98,6 +107,9 @@ const AuthenticateAdminImport = createFileRoute('/_authenticate/admin')()
|
|||||||
const AuthenticateInjectOrgDetailsOrganizationImport = createFileRoute(
|
const AuthenticateInjectOrgDetailsOrganizationImport = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization',
|
'/_authenticate/_inject-org-details/organization',
|
||||||
)()
|
)()
|
||||||
|
const AuthenticateInjectOrgDetailsSshProjectIdImport = createFileRoute(
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId',
|
||||||
|
)()
|
||||||
const AuthenticateInjectOrgDetailsSecretManagerProjectIdImport =
|
const AuthenticateInjectOrgDetailsSecretManagerProjectIdImport =
|
||||||
createFileRoute(
|
createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId',
|
'/_authenticate/_inject-org-details/secret-manager/$projectId',
|
||||||
@@ -273,6 +285,13 @@ const authLoginPageRouteRoute = authLoginPageRouteImport.update({
|
|||||||
getParentRoute: () => RestrictLoginSignupLoginRoute,
|
getParentRoute: () => RestrictLoginSignupLoginRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const AuthenticateInjectOrgDetailsSshProjectIdRoute =
|
||||||
|
AuthenticateInjectOrgDetailsSshProjectIdImport.update({
|
||||||
|
id: '/ssh/$projectId',
|
||||||
|
path: '/ssh/$projectId',
|
||||||
|
getParentRoute: () => middlewaresInjectOrgDetailsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute =
|
const AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute =
|
||||||
AuthenticateInjectOrgDetailsSecretManagerProjectIdImport.update({
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdImport.update({
|
||||||
id: '/secret-manager/$projectId',
|
id: '/secret-manager/$projectId',
|
||||||
@@ -327,6 +346,11 @@ const adminOverviewPageRouteRoute = adminOverviewPageRouteImport.update({
|
|||||||
getParentRoute: () => adminLayoutRoute,
|
getParentRoute: () => adminLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const sshLayoutRoute = sshLayoutImport.update({
|
||||||
|
id: '/_ssh-layout',
|
||||||
|
getParentRoute: () => AuthenticateInjectOrgDetailsSshProjectIdRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const secretManagerLayoutRoute = secretManagerLayoutImport.update({
|
const secretManagerLayoutRoute = secretManagerLayoutImport.update({
|
||||||
id: '/_secret-manager-layout',
|
id: '/_secret-manager-layout',
|
||||||
getParentRoute: () => AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute,
|
getParentRoute: () => AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute,
|
||||||
@@ -400,6 +424,13 @@ const organizationAccessManagementPageRouteRoute =
|
|||||||
getParentRoute: () => organizationLayoutRoute,
|
getParentRoute: () => organizationLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const projectAccessControlPageRouteSshRoute =
|
||||||
|
projectAccessControlPageRouteSshImport.update({
|
||||||
|
id: '/access-management',
|
||||||
|
path: '/access-management',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const projectAccessControlPageRouteSecretManagerRoute =
|
const projectAccessControlPageRouteSecretManagerRoute =
|
||||||
projectAccessControlPageRouteSecretManagerImport.update({
|
projectAccessControlPageRouteSecretManagerImport.update({
|
||||||
id: '/access-management',
|
id: '/access-management',
|
||||||
@@ -421,6 +452,18 @@ const projectAccessControlPageRouteCertManagerRoute =
|
|||||||
getParentRoute: () => certManagerLayoutRoute,
|
getParentRoute: () => certManagerLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const sshSettingsPageRouteRoute = sshSettingsPageRouteImport.update({
|
||||||
|
id: '/settings',
|
||||||
|
path: '/settings',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const sshOverviewPageRouteRoute = sshOverviewPageRouteImport.update({
|
||||||
|
id: '/overview',
|
||||||
|
path: '/overview',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const secretManagerSettingsPageRouteRoute =
|
const secretManagerSettingsPageRouteRoute =
|
||||||
secretManagerSettingsPageRouteImport.update({
|
secretManagerSettingsPageRouteImport.update({
|
||||||
id: '/settings',
|
id: '/settings',
|
||||||
@@ -456,6 +499,13 @@ const secretManagerIPAllowlistPageRouteRoute =
|
|||||||
getParentRoute: () => secretManagerLayoutRoute,
|
getParentRoute: () => secretManagerLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const organizationSshOverviewPageRouteRoute =
|
||||||
|
organizationSshOverviewPageRouteImport.update({
|
||||||
|
id: '/ssh/overview',
|
||||||
|
path: '/ssh/overview',
|
||||||
|
getParentRoute: () => organizationLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const organizationSecretManagerOverviewPageRouteRoute =
|
const organizationSecretManagerOverviewPageRouteRoute =
|
||||||
organizationSecretManagerOverviewPageRouteImport.update({
|
organizationSecretManagerOverviewPageRouteImport.update({
|
||||||
id: '/secret-manager/overview',
|
id: '/secret-manager/overview',
|
||||||
@@ -531,6 +581,27 @@ const certManagerCertificatesPageRouteRoute =
|
|||||||
getParentRoute: () => certManagerLayoutRoute,
|
getParentRoute: () => certManagerLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const projectRoleDetailsBySlugPageRouteSshRoute =
|
||||||
|
projectRoleDetailsBySlugPageRouteSshImport.update({
|
||||||
|
id: '/roles/$roleSlug',
|
||||||
|
path: '/roles/$roleSlug',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const projectMemberDetailsByIDPageRouteSshRoute =
|
||||||
|
projectMemberDetailsByIDPageRouteSshImport.update({
|
||||||
|
id: '/members/$membershipId',
|
||||||
|
path: '/members/$membershipId',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const projectIdentityDetailsByIDPageRouteSshRoute =
|
||||||
|
projectIdentityDetailsByIDPageRouteSshImport.update({
|
||||||
|
id: '/identities/$identityId',
|
||||||
|
path: '/identities/$identityId',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const projectRoleDetailsBySlugPageRouteSecretManagerRoute =
|
const projectRoleDetailsBySlugPageRouteSecretManagerRoute =
|
||||||
projectRoleDetailsBySlugPageRouteSecretManagerImport.update({
|
projectRoleDetailsBySlugPageRouteSecretManagerImport.update({
|
||||||
id: '/roles/$roleSlug',
|
id: '/roles/$roleSlug',
|
||||||
@@ -601,6 +672,12 @@ const projectIdentityDetailsByIDPageRouteCertManagerRoute =
|
|||||||
getParentRoute: () => certManagerLayoutRoute,
|
getParentRoute: () => certManagerLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const sshSshCaByIDPageRouteRoute = sshSshCaByIDPageRouteImport.update({
|
||||||
|
id: '/ca/$caId',
|
||||||
|
path: '/ca/$caId',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const secretManagerSecretDashboardPageRouteRoute =
|
const secretManagerSecretDashboardPageRouteRoute =
|
||||||
secretManagerSecretDashboardPageRouteImport.update({
|
secretManagerSecretDashboardPageRouteImport.update({
|
||||||
id: '/secrets/$envSlug',
|
id: '/secrets/$envSlug',
|
||||||
@@ -864,6 +941,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdImport
|
preLoaderRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdImport
|
||||||
parentRoute: typeof middlewaresInjectOrgDetailsImport
|
parentRoute: typeof middlewaresInjectOrgDetailsImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId'
|
||||||
|
path: '/ssh/$projectId'
|
||||||
|
fullPath: '/ssh/$projectId'
|
||||||
|
preLoaderRoute: typeof AuthenticateInjectOrgDetailsSshProjectIdImport
|
||||||
|
parentRoute: typeof middlewaresInjectOrgDetailsImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/access-management': {
|
'/_authenticate/_inject-org-details/organization/_layout/access-management': {
|
||||||
id: '/_authenticate/_inject-org-details/organization/_layout/access-management'
|
id: '/_authenticate/_inject-org-details/organization/_layout/access-management'
|
||||||
path: '/access-management'
|
path: '/access-management'
|
||||||
@@ -941,6 +1025,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof secretManagerLayoutImport
|
preLoaderRoute: typeof secretManagerLayoutImport
|
||||||
parentRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdImport
|
parentRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout'
|
||||||
|
path: ''
|
||||||
|
fullPath: '/ssh/$projectId'
|
||||||
|
preLoaderRoute: typeof sshLayoutImport
|
||||||
|
parentRoute: typeof AuthenticateInjectOrgDetailsSshProjectIdImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview': {
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview': {
|
||||||
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview'
|
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview'
|
||||||
path: '/overview'
|
path: '/overview'
|
||||||
@@ -1018,6 +1109,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof organizationSecretManagerOverviewPageRouteImport
|
preLoaderRoute: typeof organizationSecretManagerOverviewPageRouteImport
|
||||||
parentRoute: typeof organizationLayoutImport
|
parentRoute: typeof organizationLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/organization/_layout/ssh/overview': {
|
||||||
|
id: '/_authenticate/_inject-org-details/organization/_layout/ssh/overview'
|
||||||
|
path: '/ssh/overview'
|
||||||
|
fullPath: '/organization/ssh/overview'
|
||||||
|
preLoaderRoute: typeof organizationSshOverviewPageRouteImport
|
||||||
|
parentRoute: typeof organizationLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist': {
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist': {
|
||||||
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist'
|
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist'
|
||||||
path: '/allowlist'
|
path: '/allowlist'
|
||||||
@@ -1053,6 +1151,20 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof secretManagerSettingsPageRouteImport
|
preLoaderRoute: typeof secretManagerSettingsPageRouteImport
|
||||||
parentRoute: typeof secretManagerLayoutImport
|
parentRoute: typeof secretManagerLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/overview': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/overview'
|
||||||
|
path: '/overview'
|
||||||
|
fullPath: '/ssh/$projectId/overview'
|
||||||
|
preLoaderRoute: typeof sshOverviewPageRouteImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings'
|
||||||
|
path: '/settings'
|
||||||
|
fullPath: '/ssh/$projectId/settings'
|
||||||
|
preLoaderRoute: typeof sshSettingsPageRouteImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management': {
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management': {
|
||||||
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management'
|
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management'
|
||||||
path: '/access-management'
|
path: '/access-management'
|
||||||
@@ -1074,6 +1186,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof projectAccessControlPageRouteSecretManagerImport
|
preLoaderRoute: typeof projectAccessControlPageRouteSecretManagerImport
|
||||||
parentRoute: typeof secretManagerLayoutImport
|
parentRoute: typeof secretManagerLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management'
|
||||||
|
path: '/access-management'
|
||||||
|
fullPath: '/ssh/$projectId/access-management'
|
||||||
|
preLoaderRoute: typeof projectAccessControlPageRouteSshImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': {
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': {
|
||||||
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
||||||
path: '/ca/$caId'
|
path: '/ca/$caId'
|
||||||
@@ -1088,6 +1207,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof secretManagerSecretDashboardPageRouteImport
|
preLoaderRoute: typeof secretManagerSecretDashboardPageRouteImport
|
||||||
parentRoute: typeof secretManagerLayoutImport
|
parentRoute: typeof secretManagerLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId'
|
||||||
|
path: '/ca/$caId'
|
||||||
|
fullPath: '/ssh/$projectId/ca/$caId'
|
||||||
|
preLoaderRoute: typeof sshSshCaByIDPageRouteImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': {
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': {
|
||||||
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId'
|
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId'
|
||||||
path: '/identities/$identityId'
|
path: '/identities/$identityId'
|
||||||
@@ -1158,6 +1284,27 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof projectRoleDetailsBySlugPageRouteSecretManagerImport
|
preLoaderRoute: typeof projectRoleDetailsBySlugPageRouteSecretManagerImport
|
||||||
parentRoute: typeof secretManagerLayoutImport
|
parentRoute: typeof secretManagerLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId'
|
||||||
|
path: '/identities/$identityId'
|
||||||
|
fullPath: '/ssh/$projectId/identities/$identityId'
|
||||||
|
preLoaderRoute: typeof projectIdentityDetailsByIDPageRouteSshImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId'
|
||||||
|
path: '/members/$membershipId'
|
||||||
|
fullPath: '/ssh/$projectId/members/$membershipId'
|
||||||
|
preLoaderRoute: typeof projectMemberDetailsByIDPageRouteSshImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug': {
|
||||||
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug'
|
||||||
|
path: '/roles/$roleSlug'
|
||||||
|
fullPath: '/ssh/$projectId/roles/$roleSlug'
|
||||||
|
preLoaderRoute: typeof projectRoleDetailsBySlugPageRouteSshImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback': {
|
'/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback': {
|
||||||
id: '/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback'
|
id: '/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback'
|
||||||
path: '/app-connections/github/oauth/callback'
|
path: '/app-connections/github/oauth/callback'
|
||||||
@@ -1186,6 +1333,7 @@ interface organizationLayoutRouteChildren {
|
|||||||
organizationUserDetailsByIDPageRouteRoute: typeof organizationUserDetailsByIDPageRouteRoute
|
organizationUserDetailsByIDPageRouteRoute: typeof organizationUserDetailsByIDPageRouteRoute
|
||||||
organizationRoleByIDPageRouteRoute: typeof organizationRoleByIDPageRouteRoute
|
organizationRoleByIDPageRouteRoute: typeof organizationRoleByIDPageRouteRoute
|
||||||
organizationSecretManagerOverviewPageRouteRoute: typeof organizationSecretManagerOverviewPageRouteRoute
|
organizationSecretManagerOverviewPageRouteRoute: typeof organizationSecretManagerOverviewPageRouteRoute
|
||||||
|
organizationSshOverviewPageRouteRoute: typeof organizationSshOverviewPageRouteRoute
|
||||||
organizationAppConnectionsGithubOauthCallbackPageRouteRoute: typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
organizationAppConnectionsGithubOauthCallbackPageRouteRoute: typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,6 +1361,7 @@ const organizationLayoutRouteChildren: organizationLayoutRouteChildren = {
|
|||||||
organizationRoleByIDPageRouteRoute: organizationRoleByIDPageRouteRoute,
|
organizationRoleByIDPageRouteRoute: organizationRoleByIDPageRouteRoute,
|
||||||
organizationSecretManagerOverviewPageRouteRoute:
|
organizationSecretManagerOverviewPageRouteRoute:
|
||||||
organizationSecretManagerOverviewPageRouteRoute,
|
organizationSecretManagerOverviewPageRouteRoute,
|
||||||
|
organizationSshOverviewPageRouteRoute: organizationSshOverviewPageRouteRoute,
|
||||||
organizationAppConnectionsGithubOauthCallbackPageRouteRoute:
|
organizationAppConnectionsGithubOauthCallbackPageRouteRoute:
|
||||||
organizationAppConnectionsGithubOauthCallbackPageRouteRoute,
|
organizationAppConnectionsGithubOauthCallbackPageRouteRoute,
|
||||||
}
|
}
|
||||||
@@ -1369,11 +1518,53 @@ const AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteWithChildren =
|
|||||||
AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteChildren,
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteChildren,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface sshLayoutRouteChildren {
|
||||||
|
sshOverviewPageRouteRoute: typeof sshOverviewPageRouteRoute
|
||||||
|
sshSettingsPageRouteRoute: typeof sshSettingsPageRouteRoute
|
||||||
|
projectAccessControlPageRouteSshRoute: typeof projectAccessControlPageRouteSshRoute
|
||||||
|
sshSshCaByIDPageRouteRoute: typeof sshSshCaByIDPageRouteRoute
|
||||||
|
projectIdentityDetailsByIDPageRouteSshRoute: typeof projectIdentityDetailsByIDPageRouteSshRoute
|
||||||
|
projectMemberDetailsByIDPageRouteSshRoute: typeof projectMemberDetailsByIDPageRouteSshRoute
|
||||||
|
projectRoleDetailsBySlugPageRouteSshRoute: typeof projectRoleDetailsBySlugPageRouteSshRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const sshLayoutRouteChildren: sshLayoutRouteChildren = {
|
||||||
|
sshOverviewPageRouteRoute: sshOverviewPageRouteRoute,
|
||||||
|
sshSettingsPageRouteRoute: sshSettingsPageRouteRoute,
|
||||||
|
projectAccessControlPageRouteSshRoute: projectAccessControlPageRouteSshRoute,
|
||||||
|
sshSshCaByIDPageRouteRoute: sshSshCaByIDPageRouteRoute,
|
||||||
|
projectIdentityDetailsByIDPageRouteSshRoute:
|
||||||
|
projectIdentityDetailsByIDPageRouteSshRoute,
|
||||||
|
projectMemberDetailsByIDPageRouteSshRoute:
|
||||||
|
projectMemberDetailsByIDPageRouteSshRoute,
|
||||||
|
projectRoleDetailsBySlugPageRouteSshRoute:
|
||||||
|
projectRoleDetailsBySlugPageRouteSshRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const sshLayoutRouteWithChildren = sshLayoutRoute._addFileChildren(
|
||||||
|
sshLayoutRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface AuthenticateInjectOrgDetailsSshProjectIdRouteChildren {
|
||||||
|
sshLayoutRoute: typeof sshLayoutRouteWithChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthenticateInjectOrgDetailsSshProjectIdRouteChildren: AuthenticateInjectOrgDetailsSshProjectIdRouteChildren =
|
||||||
|
{
|
||||||
|
sshLayoutRoute: sshLayoutRouteWithChildren,
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthenticateInjectOrgDetailsSshProjectIdRouteWithChildren =
|
||||||
|
AuthenticateInjectOrgDetailsSshProjectIdRoute._addFileChildren(
|
||||||
|
AuthenticateInjectOrgDetailsSshProjectIdRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
interface middlewaresInjectOrgDetailsRouteChildren {
|
interface middlewaresInjectOrgDetailsRouteChildren {
|
||||||
AuthenticateInjectOrgDetailsOrganizationRoute: typeof AuthenticateInjectOrgDetailsOrganizationRouteWithChildren
|
AuthenticateInjectOrgDetailsOrganizationRoute: typeof AuthenticateInjectOrgDetailsOrganizationRouteWithChildren
|
||||||
AuthenticateInjectOrgDetailsCertManagerProjectIdRoute: typeof AuthenticateInjectOrgDetailsCertManagerProjectIdRouteWithChildren
|
AuthenticateInjectOrgDetailsCertManagerProjectIdRoute: typeof AuthenticateInjectOrgDetailsCertManagerProjectIdRouteWithChildren
|
||||||
AuthenticateInjectOrgDetailsKmsProjectIdRoute: typeof AuthenticateInjectOrgDetailsKmsProjectIdRouteWithChildren
|
AuthenticateInjectOrgDetailsKmsProjectIdRoute: typeof AuthenticateInjectOrgDetailsKmsProjectIdRouteWithChildren
|
||||||
AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteWithChildren
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteWithChildren
|
||||||
|
AuthenticateInjectOrgDetailsSshProjectIdRoute: typeof AuthenticateInjectOrgDetailsSshProjectIdRouteWithChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
const middlewaresInjectOrgDetailsRouteChildren: middlewaresInjectOrgDetailsRouteChildren =
|
const middlewaresInjectOrgDetailsRouteChildren: middlewaresInjectOrgDetailsRouteChildren =
|
||||||
@@ -1386,6 +1577,8 @@ const middlewaresInjectOrgDetailsRouteChildren: middlewaresInjectOrgDetailsRoute
|
|||||||
AuthenticateInjectOrgDetailsKmsProjectIdRouteWithChildren,
|
AuthenticateInjectOrgDetailsKmsProjectIdRouteWithChildren,
|
||||||
AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute:
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdRoute:
|
||||||
AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteWithChildren,
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteWithChildren,
|
||||||
|
AuthenticateInjectOrgDetailsSshProjectIdRoute:
|
||||||
|
AuthenticateInjectOrgDetailsSshProjectIdRouteWithChildren,
|
||||||
}
|
}
|
||||||
|
|
||||||
const middlewaresInjectOrgDetailsRouteWithChildren =
|
const middlewaresInjectOrgDetailsRouteWithChildren =
|
||||||
@@ -1562,6 +1755,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
||||||
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
||||||
'/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren
|
'/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren
|
||||||
|
'/ssh/$projectId': typeof sshLayoutRouteWithChildren
|
||||||
'/organization/access-management': typeof organizationAccessManagementPageRouteRoute
|
'/organization/access-management': typeof organizationAccessManagementPageRouteRoute
|
||||||
'/organization/admin': typeof organizationAdminPageRouteRoute
|
'/organization/admin': typeof organizationAdminPageRouteRoute
|
||||||
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
||||||
@@ -1581,16 +1775,21 @@ export interface FileRoutesByFullPath {
|
|||||||
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
|
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
|
||||||
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
|
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
|
||||||
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
|
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
|
||||||
|
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
|
||||||
'/secret-manager/$projectId/allowlist': typeof secretManagerIPAllowlistPageRouteRoute
|
'/secret-manager/$projectId/allowlist': typeof secretManagerIPAllowlistPageRouteRoute
|
||||||
'/secret-manager/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute
|
'/secret-manager/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute
|
||||||
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
||||||
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
||||||
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
||||||
|
'/ssh/$projectId/overview': typeof sshOverviewPageRouteRoute
|
||||||
|
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
|
||||||
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
||||||
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
||||||
|
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
|
||||||
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
||||||
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
||||||
|
'/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
||||||
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
||||||
'/cert-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute
|
'/cert-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute
|
||||||
'/cert-manager/$projectId/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute
|
'/cert-manager/$projectId/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute
|
||||||
@@ -1601,6 +1800,9 @@ export interface FileRoutesByFullPath {
|
|||||||
'/secret-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
||||||
'/secret-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
||||||
'/secret-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute
|
||||||
|
'/ssh/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSshRoute
|
||||||
|
'/ssh/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSshRoute
|
||||||
|
'/ssh/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSshRoute
|
||||||
'/organization/app-connections/github/oauth/callback': typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
'/organization/app-connections/github/oauth/callback': typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1630,6 +1832,7 @@ export interface FileRoutesByTo {
|
|||||||
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
||||||
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
||||||
'/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren
|
'/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren
|
||||||
|
'/ssh/$projectId': typeof sshLayoutRouteWithChildren
|
||||||
'/organization/access-management': typeof organizationAccessManagementPageRouteRoute
|
'/organization/access-management': typeof organizationAccessManagementPageRouteRoute
|
||||||
'/organization/admin': typeof organizationAdminPageRouteRoute
|
'/organization/admin': typeof organizationAdminPageRouteRoute
|
||||||
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
||||||
@@ -1649,16 +1852,21 @@ export interface FileRoutesByTo {
|
|||||||
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
|
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
|
||||||
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
|
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
|
||||||
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
|
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
|
||||||
|
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
|
||||||
'/secret-manager/$projectId/allowlist': typeof secretManagerIPAllowlistPageRouteRoute
|
'/secret-manager/$projectId/allowlist': typeof secretManagerIPAllowlistPageRouteRoute
|
||||||
'/secret-manager/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute
|
'/secret-manager/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute
|
||||||
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
||||||
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
||||||
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
||||||
|
'/ssh/$projectId/overview': typeof sshOverviewPageRouteRoute
|
||||||
|
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
|
||||||
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
||||||
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
||||||
|
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
|
||||||
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
||||||
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
||||||
|
'/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
||||||
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
||||||
'/cert-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute
|
'/cert-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute
|
||||||
'/cert-manager/$projectId/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute
|
'/cert-manager/$projectId/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute
|
||||||
@@ -1669,6 +1877,9 @@ export interface FileRoutesByTo {
|
|||||||
'/secret-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
||||||
'/secret-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
||||||
'/secret-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute
|
||||||
|
'/ssh/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSshRoute
|
||||||
|
'/ssh/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSshRoute
|
||||||
|
'/ssh/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSshRoute
|
||||||
'/organization/app-connections/github/oauth/callback': typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
'/organization/app-connections/github/oauth/callback': typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1708,6 +1919,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/kms/$projectId': typeof AuthenticateInjectOrgDetailsKmsProjectIdRouteWithChildren
|
'/_authenticate/_inject-org-details/kms/$projectId': typeof AuthenticateInjectOrgDetailsKmsProjectIdRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/organization/_layout': typeof organizationLayoutRouteWithChildren
|
'/_authenticate/_inject-org-details/organization/_layout': typeof organizationLayoutRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteWithChildren
|
'/_authenticate/_inject-org-details/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdRouteWithChildren
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId': typeof AuthenticateInjectOrgDetailsSshProjectIdRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/access-management': typeof organizationAccessManagementPageRouteRoute
|
'/_authenticate/_inject-org-details/organization/_layout/access-management': typeof organizationAccessManagementPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/admin': typeof organizationAdminPageRouteRoute
|
'/_authenticate/_inject-org-details/organization/_layout/admin': typeof organizationAdminPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
'/_authenticate/_inject-org-details/organization/_layout/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
||||||
@@ -1719,6 +1931,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout': typeof certManagerLayoutRouteWithChildren
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout': typeof certManagerLayoutRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren
|
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout': typeof secretManagerLayoutRouteWithChildren
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout': typeof secretManagerLayoutRouteWithChildren
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout': typeof sshLayoutRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/overview': typeof kmsOverviewPageRouteRoute
|
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/overview': typeof kmsOverviewPageRouteRoute
|
||||||
@@ -1730,16 +1943,21 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/organization/_layout/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
|
'/_authenticate/_inject-org-details/organization/_layout/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
|
'/_authenticate/_inject-org-details/organization/_layout/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
|
'/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/organization/_layout/ssh/overview': typeof organizationSshOverviewPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist': typeof secretManagerIPAllowlistPageRouteRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist': typeof secretManagerIPAllowlistPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval': typeof secretManagerSecretApprovalsPageRouteRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval': typeof secretManagerSecretApprovalsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/overview': typeof secretManagerOverviewPageRouteRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/overview': typeof secretManagerOverviewPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings': typeof secretManagerSettingsPageRouteRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings': typeof secretManagerSettingsPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/overview': typeof sshOverviewPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management': typeof projectAccessControlPageRouteKmsRoute
|
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management': typeof projectAccessControlPageRouteKmsRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute
|
||||||
@@ -1750,6 +1968,9 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSshRoute
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSshRoute
|
||||||
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSshRoute
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback': typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
'/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback': typeof organizationAppConnectionsGithubOauthCallbackPageRouteRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1785,6 +2006,7 @@ export interface FileRouteTypes {
|
|||||||
| '/cert-manager/$projectId'
|
| '/cert-manager/$projectId'
|
||||||
| '/kms/$projectId'
|
| '/kms/$projectId'
|
||||||
| '/secret-manager/$projectId'
|
| '/secret-manager/$projectId'
|
||||||
|
| '/ssh/$projectId'
|
||||||
| '/organization/access-management'
|
| '/organization/access-management'
|
||||||
| '/organization/admin'
|
| '/organization/admin'
|
||||||
| '/organization/audit-logs'
|
| '/organization/audit-logs'
|
||||||
@@ -1804,16 +2026,21 @@ export interface FileRouteTypes {
|
|||||||
| '/organization/members/$membershipId'
|
| '/organization/members/$membershipId'
|
||||||
| '/organization/roles/$roleId'
|
| '/organization/roles/$roleId'
|
||||||
| '/organization/secret-manager/overview'
|
| '/organization/secret-manager/overview'
|
||||||
|
| '/organization/ssh/overview'
|
||||||
| '/secret-manager/$projectId/allowlist'
|
| '/secret-manager/$projectId/allowlist'
|
||||||
| '/secret-manager/$projectId/approval'
|
| '/secret-manager/$projectId/approval'
|
||||||
| '/secret-manager/$projectId/overview'
|
| '/secret-manager/$projectId/overview'
|
||||||
| '/secret-manager/$projectId/secret-rotation'
|
| '/secret-manager/$projectId/secret-rotation'
|
||||||
| '/secret-manager/$projectId/settings'
|
| '/secret-manager/$projectId/settings'
|
||||||
|
| '/ssh/$projectId/overview'
|
||||||
|
| '/ssh/$projectId/settings'
|
||||||
| '/cert-manager/$projectId/access-management'
|
| '/cert-manager/$projectId/access-management'
|
||||||
| '/kms/$projectId/access-management'
|
| '/kms/$projectId/access-management'
|
||||||
| '/secret-manager/$projectId/access-management'
|
| '/secret-manager/$projectId/access-management'
|
||||||
|
| '/ssh/$projectId/access-management'
|
||||||
| '/cert-manager/$projectId/ca/$caId'
|
| '/cert-manager/$projectId/ca/$caId'
|
||||||
| '/secret-manager/$projectId/secrets/$envSlug'
|
| '/secret-manager/$projectId/secrets/$envSlug'
|
||||||
|
| '/ssh/$projectId/ca/$caId'
|
||||||
| '/cert-manager/$projectId/identities/$identityId'
|
| '/cert-manager/$projectId/identities/$identityId'
|
||||||
| '/cert-manager/$projectId/members/$membershipId'
|
| '/cert-manager/$projectId/members/$membershipId'
|
||||||
| '/cert-manager/$projectId/pki-collections/$collectionId'
|
| '/cert-manager/$projectId/pki-collections/$collectionId'
|
||||||
@@ -1824,6 +2051,9 @@ export interface FileRouteTypes {
|
|||||||
| '/secret-manager/$projectId/identities/$identityId'
|
| '/secret-manager/$projectId/identities/$identityId'
|
||||||
| '/secret-manager/$projectId/members/$membershipId'
|
| '/secret-manager/$projectId/members/$membershipId'
|
||||||
| '/secret-manager/$projectId/roles/$roleSlug'
|
| '/secret-manager/$projectId/roles/$roleSlug'
|
||||||
|
| '/ssh/$projectId/identities/$identityId'
|
||||||
|
| '/ssh/$projectId/members/$membershipId'
|
||||||
|
| '/ssh/$projectId/roles/$roleSlug'
|
||||||
| '/organization/app-connections/github/oauth/callback'
|
| '/organization/app-connections/github/oauth/callback'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
@@ -1852,6 +2082,7 @@ export interface FileRouteTypes {
|
|||||||
| '/cert-manager/$projectId'
|
| '/cert-manager/$projectId'
|
||||||
| '/kms/$projectId'
|
| '/kms/$projectId'
|
||||||
| '/secret-manager/$projectId'
|
| '/secret-manager/$projectId'
|
||||||
|
| '/ssh/$projectId'
|
||||||
| '/organization/access-management'
|
| '/organization/access-management'
|
||||||
| '/organization/admin'
|
| '/organization/admin'
|
||||||
| '/organization/audit-logs'
|
| '/organization/audit-logs'
|
||||||
@@ -1871,16 +2102,21 @@ export interface FileRouteTypes {
|
|||||||
| '/organization/members/$membershipId'
|
| '/organization/members/$membershipId'
|
||||||
| '/organization/roles/$roleId'
|
| '/organization/roles/$roleId'
|
||||||
| '/organization/secret-manager/overview'
|
| '/organization/secret-manager/overview'
|
||||||
|
| '/organization/ssh/overview'
|
||||||
| '/secret-manager/$projectId/allowlist'
|
| '/secret-manager/$projectId/allowlist'
|
||||||
| '/secret-manager/$projectId/approval'
|
| '/secret-manager/$projectId/approval'
|
||||||
| '/secret-manager/$projectId/overview'
|
| '/secret-manager/$projectId/overview'
|
||||||
| '/secret-manager/$projectId/secret-rotation'
|
| '/secret-manager/$projectId/secret-rotation'
|
||||||
| '/secret-manager/$projectId/settings'
|
| '/secret-manager/$projectId/settings'
|
||||||
|
| '/ssh/$projectId/overview'
|
||||||
|
| '/ssh/$projectId/settings'
|
||||||
| '/cert-manager/$projectId/access-management'
|
| '/cert-manager/$projectId/access-management'
|
||||||
| '/kms/$projectId/access-management'
|
| '/kms/$projectId/access-management'
|
||||||
| '/secret-manager/$projectId/access-management'
|
| '/secret-manager/$projectId/access-management'
|
||||||
|
| '/ssh/$projectId/access-management'
|
||||||
| '/cert-manager/$projectId/ca/$caId'
|
| '/cert-manager/$projectId/ca/$caId'
|
||||||
| '/secret-manager/$projectId/secrets/$envSlug'
|
| '/secret-manager/$projectId/secrets/$envSlug'
|
||||||
|
| '/ssh/$projectId/ca/$caId'
|
||||||
| '/cert-manager/$projectId/identities/$identityId'
|
| '/cert-manager/$projectId/identities/$identityId'
|
||||||
| '/cert-manager/$projectId/members/$membershipId'
|
| '/cert-manager/$projectId/members/$membershipId'
|
||||||
| '/cert-manager/$projectId/pki-collections/$collectionId'
|
| '/cert-manager/$projectId/pki-collections/$collectionId'
|
||||||
@@ -1891,6 +2127,9 @@ export interface FileRouteTypes {
|
|||||||
| '/secret-manager/$projectId/identities/$identityId'
|
| '/secret-manager/$projectId/identities/$identityId'
|
||||||
| '/secret-manager/$projectId/members/$membershipId'
|
| '/secret-manager/$projectId/members/$membershipId'
|
||||||
| '/secret-manager/$projectId/roles/$roleSlug'
|
| '/secret-manager/$projectId/roles/$roleSlug'
|
||||||
|
| '/ssh/$projectId/identities/$identityId'
|
||||||
|
| '/ssh/$projectId/members/$membershipId'
|
||||||
|
| '/ssh/$projectId/roles/$roleSlug'
|
||||||
| '/organization/app-connections/github/oauth/callback'
|
| '/organization/app-connections/github/oauth/callback'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
@@ -1928,6 +2167,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/kms/$projectId'
|
| '/_authenticate/_inject-org-details/kms/$projectId'
|
||||||
| '/_authenticate/_inject-org-details/organization/_layout'
|
| '/_authenticate/_inject-org-details/organization/_layout'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId'
|
||||||
| '/_authenticate/_inject-org-details/organization/_layout/access-management'
|
| '/_authenticate/_inject-org-details/organization/_layout/access-management'
|
||||||
| '/_authenticate/_inject-org-details/organization/_layout/admin'
|
| '/_authenticate/_inject-org-details/organization/_layout/admin'
|
||||||
| '/_authenticate/_inject-org-details/organization/_layout/audit-logs'
|
| '/_authenticate/_inject-org-details/organization/_layout/audit-logs'
|
||||||
@@ -1939,6 +2179,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout'
|
||||||
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout'
|
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings'
|
||||||
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/overview'
|
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/overview'
|
||||||
@@ -1950,16 +2191,21 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/organization/_layout/members/$membershipId'
|
| '/_authenticate/_inject-org-details/organization/_layout/members/$membershipId'
|
||||||
| '/_authenticate/_inject-org-details/organization/_layout/roles/$roleId'
|
| '/_authenticate/_inject-org-details/organization/_layout/roles/$roleId'
|
||||||
| '/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview'
|
| '/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview'
|
||||||
|
| '/_authenticate/_inject-org-details/organization/_layout/ssh/overview'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/overview'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/overview'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/overview'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management'
|
||||||
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management'
|
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId'
|
||||||
@@ -1970,6 +2216,9 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId'
|
||||||
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug'
|
||||||
| '/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback'
|
| '/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
@@ -2068,7 +2317,8 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/organization",
|
"/_authenticate/_inject-org-details/organization",
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId",
|
"/_authenticate/_inject-org-details/cert-manager/$projectId",
|
||||||
"/_authenticate/_inject-org-details/kms/$projectId",
|
"/_authenticate/_inject-org-details/kms/$projectId",
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/_authenticate/admin": {
|
"/_authenticate/admin": {
|
||||||
@@ -2206,6 +2456,7 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/organization/_layout/members/$membershipId",
|
"/_authenticate/_inject-org-details/organization/_layout/members/$membershipId",
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/roles/$roleId",
|
"/_authenticate/_inject-org-details/organization/_layout/roles/$roleId",
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview",
|
"/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview",
|
||||||
|
"/_authenticate/_inject-org-details/organization/_layout/ssh/overview",
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback"
|
"/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2216,6 +2467,13 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId": {
|
||||||
|
"filePath": "",
|
||||||
|
"parent": "/_authenticate/_inject-org-details",
|
||||||
|
"children": [
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
]
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/access-management": {
|
"/_authenticate/_inject-org-details/organization/_layout/access-management": {
|
||||||
"filePath": "organization/AccessManagementPage/route.tsx",
|
"filePath": "organization/AccessManagementPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/organization/_layout"
|
"parent": "/_authenticate/_inject-org-details/organization/_layout"
|
||||||
@@ -2290,6 +2548,19 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout": {
|
||||||
|
"filePath": "ssh/layout.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId",
|
||||||
|
"children": [
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/overview",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId",
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug"
|
||||||
|
]
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview": {
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview": {
|
||||||
"filePath": "cert-manager/CertificatesPage/route.tsx",
|
"filePath": "cert-manager/CertificatesPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
||||||
@@ -2334,6 +2605,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "organization/SecretManagerOverviewPage/route.tsx",
|
"filePath": "organization/SecretManagerOverviewPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/organization/_layout"
|
"parent": "/_authenticate/_inject-org-details/organization/_layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/organization/_layout/ssh/overview": {
|
||||||
|
"filePath": "organization/SshOverviewPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/organization/_layout"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist": {
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/allowlist": {
|
||||||
"filePath": "secret-manager/IPAllowlistPage/route.tsx",
|
"filePath": "secret-manager/IPAllowlistPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
@@ -2354,6 +2629,14 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "secret-manager/SettingsPage/route.tsx",
|
"filePath": "secret-manager/SettingsPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/overview": {
|
||||||
|
"filePath": "ssh/OverviewPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings": {
|
||||||
|
"filePath": "ssh/SettingsPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management": {
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management": {
|
||||||
"filePath": "project/AccessControlPage/route-cert-manager.tsx",
|
"filePath": "project/AccessControlPage/route-cert-manager.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
||||||
@@ -2366,6 +2649,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "project/AccessControlPage/route-secret-manager.tsx",
|
"filePath": "project/AccessControlPage/route-secret-manager.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management": {
|
||||||
|
"filePath": "project/AccessControlPage/route-ssh.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId": {
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId": {
|
||||||
"filePath": "cert-manager/CertAuthDetailsByIDPage/route.tsx",
|
"filePath": "cert-manager/CertAuthDetailsByIDPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
||||||
@@ -2374,6 +2661,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "secret-manager/SecretDashboardPage/route.tsx",
|
"filePath": "secret-manager/SecretDashboardPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId": {
|
||||||
|
"filePath": "ssh/SshCaByIDPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId": {
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId": {
|
||||||
"filePath": "project/IdentityDetailsByIDPage/route-cert-manager.tsx",
|
"filePath": "project/IdentityDetailsByIDPage/route-cert-manager.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
||||||
@@ -2414,6 +2705,18 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "project/RoleDetailsBySlugPage/route-secret-manager.tsx",
|
"filePath": "project/RoleDetailsBySlugPage/route-secret-manager.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId": {
|
||||||
|
"filePath": "project/IdentityDetailsByIDPage/route-ssh.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId": {
|
||||||
|
"filePath": "project/MemberDetailsByIDPage/route-ssh.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug": {
|
||||||
|
"filePath": "project/RoleDetailsBySlugPage/route-ssh.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback": {
|
"/_authenticate/_inject-org-details/organization/_layout/app-connections/github/oauth/callback": {
|
||||||
"filePath": "organization/AppConnections/GithubOauthCallbackPage/route.tsx",
|
"filePath": "organization/AppConnections/GithubOauthCallbackPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/organization/_layout"
|
"parent": "/_authenticate/_inject-org-details/organization/_layout"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const organizationRoutes = route("/organization", [
|
|||||||
layout("organization/layout.tsx", [
|
layout("organization/layout.tsx", [
|
||||||
route("/secret-manager/overview", "organization/SecretManagerOverviewPage/route.tsx"),
|
route("/secret-manager/overview", "organization/SecretManagerOverviewPage/route.tsx"),
|
||||||
route("/cert-manager/overview", "organization/CertManagerOverviewPage/route.tsx"),
|
route("/cert-manager/overview", "organization/CertManagerOverviewPage/route.tsx"),
|
||||||
|
route("/ssh/overview", "organization/SshOverviewPage/route.tsx"),
|
||||||
route("/kms/overview", "organization/KmsOverviewPage/route.tsx"),
|
route("/kms/overview", "organization/KmsOverviewPage/route.tsx"),
|
||||||
route("/access-management", "organization/AccessManagementPage/route.tsx"),
|
route("/access-management", "organization/AccessManagementPage/route.tsx"),
|
||||||
route("/admin", "organization/AdminPage/route.tsx"),
|
route("/admin", "organization/AdminPage/route.tsx"),
|
||||||
@@ -70,6 +71,18 @@ const kmsRoutes = route("/kms/$projectId", [
|
|||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const sshRoutes = route("/ssh/$projectId", [
|
||||||
|
layout("ssh-layout", "ssh/layout.tsx", [
|
||||||
|
route("/overview", "ssh/OverviewPage/route.tsx"),
|
||||||
|
route("/ca/$caId", "ssh/SshCaByIDPage/route.tsx"),
|
||||||
|
route("/settings", "ssh/SettingsPage/route.tsx"),
|
||||||
|
route("/access-management", "project/AccessControlPage/route-ssh.tsx"),
|
||||||
|
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-ssh.tsx"),
|
||||||
|
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-ssh.tsx"),
|
||||||
|
route("/members/$membershipId", "project/MemberDetailsByIDPage/route-ssh.tsx")
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
export const routes = rootRoute("root.tsx", [
|
export const routes = rootRoute("root.tsx", [
|
||||||
index("index.tsx"),
|
index("index.tsx"),
|
||||||
route("/shared/secret/$secretId", "public/ViewSharedSecretByIDPage/route.tsx"),
|
route("/shared/secret/$secretId", "public/ViewSharedSecretByIDPage/route.tsx"),
|
||||||
@@ -104,7 +117,8 @@ export const routes = rootRoute("root.tsx", [
|
|||||||
organizationRoutes,
|
organizationRoutes,
|
||||||
secretManagerRoutes,
|
secretManagerRoutes,
|
||||||
certManagerRoutes,
|
certManagerRoutes,
|
||||||
kmsRoutes
|
kmsRoutes,
|
||||||
|
sshRoutes
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user