tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function UnstableTableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ );
+}
+
+function UnstableTableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function UnstableTableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function UnstableTableCaption({ className, ...props }: React.ComponentProps<"caption">) {
+ return (
+
+ );
+}
+
+export {
+ UnstableTable,
+ UnstableTableBody,
+ UnstableTableCaption,
+ UnstableTableCell,
+ UnstableTableFooter,
+ UnstableTableHead,
+ UnstableTableHeader,
+ UnstableTableRow
+};
diff --git a/frontend/src/components/v3/generic/Table/index.ts b/frontend/src/components/v3/generic/Table/index.ts
new file mode 100644
index 0000000000..e40efa4761
--- /dev/null
+++ b/frontend/src/components/v3/generic/Table/index.ts
@@ -0,0 +1 @@
+export * from "./Table";
diff --git a/frontend/src/components/v3/generic/index.ts b/frontend/src/components/v3/generic/index.ts
index ae21190ba6..de3288e4c6 100644
--- a/frontend/src/components/v3/generic/index.ts
+++ b/frontend/src/components/v3/generic/index.ts
@@ -1 +1,12 @@
+export * from "./Alert";
export * from "./Badge";
+export * from "./Button";
+export * from "./ButtonGroup";
+export * from "./Card";
+export * from "./Detail";
+export * from "./Dropdown";
+export * from "./Empty";
+export * from "./IconButton";
+export * from "./PageLoader";
+export * from "./Separator";
+export * from "./Table";
diff --git a/frontend/src/components/v3/platform/ScopeIcons.tsx b/frontend/src/components/v3/platform/ScopeIcons.tsx
index 8f87123197..9f21ce2502 100644
--- a/frontend/src/components/v3/platform/ScopeIcons.tsx
+++ b/frontend/src/components/v3/platform/ScopeIcons.tsx
@@ -1,8 +1,8 @@
import { BoxesIcon, BoxIcon, Building2Icon, ServerIcon } from "lucide-react";
-const InstanceIcon = ServerIcon;
-const OrgIcon = Building2Icon;
-const SubOrgIcon = BoxesIcon;
-const ProjectIcon = BoxIcon;
-
-export { InstanceIcon, OrgIcon, ProjectIcon, SubOrgIcon };
+export {
+ ServerIcon as InstanceIcon,
+ Building2Icon as OrgIcon,
+ BoxIcon as ProjectIcon,
+ BoxesIcon as SubOrgIcon
+};
diff --git a/frontend/src/index.css b/frontend/src/index.css
index baa613b364..afd6c1b57b 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,4 +1,5 @@
@import "tailwindcss";
+@import "tw-animate-css";
@source not "../public";
@@ -39,7 +40,7 @@
/* Colors v2 */
--color-background: #19191c;
- --color-foreground: white;
+ --color-foreground: #ebebeb;
--color-success: #2ecc71;
--color-info: #63b0bd;
--color-warning: #f1c40f;
@@ -48,6 +49,14 @@
--color-sub-org: #96ff59;
--color-project: #e0ed34;
--color-neutral: #adaeb0;
+ --color-border: #323439;
+ --color-label: #adaeb0;
+ --color-muted: #707174;
+ --color-popover: #111419;
+ --color-ring: #2d2f33;
+ --color-container: #16181a;
+ --color-accent: #7d7f80;
+ --color-muted-foreground: ;
/*legacy color schema */
--color-org-v1: #30b3ff;
diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx
index 9e7484a84a..d8fbd73d74 100644
--- a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx
+++ b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx
@@ -1,24 +1,36 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { subject } from "@casl/ability";
-import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { DropdownMenu } from "@radix-ui/react-dropdown-menu";
import { useQuery } from "@tanstack/react-query";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
-import { formatRelative } from "date-fns";
+import { ChevronLeftIcon, EllipsisIcon, InfoIcon } from "lucide-react";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan, ProjectPermissionCan } from "@app/components/permissions";
import {
- Alert,
- AlertDescription,
- Button,
ConfirmActionModal,
DeleteActionModal,
EmptyState,
PageHeader,
- Spinner
+ Tooltip
} from "@app/components/v2";
+import {
+ OrgIcon,
+ UnstableAlert,
+ UnstableAlertDescription,
+ UnstableAlertTitle,
+ UnstableButton,
+ UnstableCard,
+ UnstableCardContent,
+ UnstableCardDescription,
+ UnstableCardHeader,
+ UnstableCardTitle,
+ UnstableDropdownMenuContent,
+ UnstableDropdownMenuItem,
+ UnstableDropdownMenuTrigger,
+ UnstablePageLoader
+} from "@app/components/v3";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
@@ -36,7 +48,7 @@ import {
useGetProjectIdentityMembershipV2
} from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
-import { projectIdentityQuery } from "@app/hooks/api/projectIdentity";
+import { projectIdentityQuery, useDeleteProjectIdentity } from "@app/hooks/api/projectIdentity";
import { ProjectIdentityAuthenticationSection } from "@app/pages/project/IdentityDetailsByIDPage/components/ProjectIdentityAuthSection";
import { ProjectIdentityDetailsSection } from "@app/pages/project/IdentityDetailsByIDPage/components/ProjectIdentityDetailsSection";
import { ProjectAccessControlTabs } from "@app/types/project";
@@ -56,8 +68,7 @@ const Page = () => {
const { data: identityMembershipDetails, isPending: isMembershipDetailsLoading } =
useGetProjectIdentityMembershipV2(projectId, identityId);
- const { mutateAsync: deleteMutateAsync, isPending: isDeletingIdentity } =
- useDeleteProjectIdentityMembership();
+ const { mutateAsync: removeIdentityMutateAsync } = useDeleteProjectIdentityMembership();
const isProjectIdentity = Boolean(identityMembershipDetails?.identity.projectId);
const isNonScopedIdentity =
@@ -75,7 +86,10 @@ const Page = () => {
enabled: isProjectIdentity
});
+ const { mutateAsync: deleteIdentity } = useDeleteProjectIdentity();
+
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+ "removeIdentity",
"deleteIdentity",
"assumePrivileges"
] as const);
@@ -104,7 +118,7 @@ const Page = () => {
};
const onRemoveIdentitySubmit = async () => {
- await deleteMutateAsync({
+ await removeIdentityMutateAsync({
identityId,
projectId
});
@@ -112,7 +126,7 @@ const Page = () => {
text: "Successfully removed machine identity from project",
type: "success"
});
- handlePopUpClose("deleteIdentity");
+ handlePopUpClose("removeIdentity");
navigate({
to: `${getProjectBaseURL(currentProject.type)}/access-management` as const,
params: {
@@ -125,16 +139,35 @@ const Page = () => {
});
};
+ const handleDeleteIdentity = async () => {
+ if (!identity) return;
+
+ try {
+ await deleteIdentity({
+ identityId: identity.id,
+ projectId: identity.projectId!
+ });
+
+ navigate({
+ to: `${getProjectBaseURL(currentProject.type)}/access-management`,
+ search: {
+ selectedTab: "identities"
+ }
+ });
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to delete project machine identity"
+ });
+ }
+ };
+
if (isMembershipDetailsLoading || (isProjectIdentity && isProjectIdentityPending)) {
- return (
-
-
-
- );
+ return ;
}
return (
-
+
{identityMembershipDetails ? (
<>
{
search={{
selectedTab: ProjectAccessControlTabs.Identities
}}
- className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
+ className="mb-3 flex w-fit items-center gap-x-1 text-sm text-mineshaft-400 transition duration-100 hover:text-mineshaft-400/80"
>
-
+
Project Machine Identities
-
-
{
- navigator.clipboard.writeText(identityMembershipDetails.id);
- createNotification({
- text: "Membership ID copied to clipboard",
- type: "success"
- });
- }}
- >
- Copy Membership ID
-
-
- {(isAllowed) => (
- handlePopUpOpen("assumePrivileges")}
- >
- Assume Privileges
-
- )}
-
- {!isProjectIdentity && (
+
+
+
+ Options
+
+
+
+
+ {
+ navigator.clipboard.writeText(identityMembershipDetails.id);
+ createNotification({
+ text: "Machine identity ID copied to clipboard",
+ type: "info"
+ });
+ }}
+ >
+ Copy Machine Identity ID
+
+
+ {(isAllowed) => (
+ handlePopUpOpen("assumePrivileges")}
+ >
+ Assume Privileges
+
+
+
+
+
+
+ )}
+
{(isAllowed) => (
- handlePopUpOpen("deleteIdentity")}
+ onClick={() =>
+ isProjectIdentity
+ ? handlePopUpOpen("deleteIdentity")
+ : handlePopUpOpen("removeIdentity")
+ }
>
- Remove Machine Identity
-
+ {isProjectIdentity ? "Delete Machine Identity" : "Remove From Project"}
+
)}
- )}
-
+
+
- {!isProjectIdentity && (
-
-
- This machine identity is managed by your organization.{" "}
-
- {(isAllowed) =>
- isAllowed ? (
-
-
- Click here to manage machine identity.
-
-
- ) : null
- }
-
-
-
- )}
-
- {identity ? (
-
-
+
+
+
+
+ {identity ? (
refetchIdentity()}
/>
-
- ) : (
-
- )}
-
+ ) : (
+
+
+ Authentication
+
+ Configure authentication methods
+
+
+
+
+
+
+ Machine identity managed by organization
+
+
+
+ This machine identity's authentication methods are controlled by your
+ organization. To make changes,{" "}
+
+ {(isAllowed) =>
+ isAllowed ? (
+
+ go to organization access control
+
+ ) : null
+ }
+
+ .
+
+
+
+
+
+ )}
{
handlePopUpToggle("deleteIdentity", isOpen)}
+ onChange={(isOpen) => handlePopUpToggle("removeIdentity", isOpen)}
deleteKey="remove"
onDeleteApproved={() => onRemoveIdentitySubmit()}
/>
@@ -292,6 +338,13 @@ const Page = () => {
onConfirmed={handleAssumePrivileges}
buttonText="Confirm"
/>
+ handlePopUpToggle("deleteIdentity", isOpen)}
+ deleteKey="confirm"
+ onDeleteApproved={handleDeleteIdentity}
+ />
>
) : (
diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx
index 956c01d694..fb48245a67 100644
--- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx
+++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx
@@ -1,6 +1,6 @@
import { Controller, FormProvider, useForm } from "react-hook-form";
import { subject } from "@casl/ability";
-import { faCaretDown, faChevronLeft, faClock, faSave } from "@fortawesome/free-solid-svg-icons";
+import { faCaretDown, faClock, faSave } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { format, formatDistance } from "date-fns";
@@ -21,6 +21,7 @@ import {
Tag,
Tooltip
} from "@app/components/v2";
+import { UnstableSeparator } from "@app/components/v3";
import {
ProjectPermissionIdentityActions,
ProjectPermissionSub,
@@ -180,55 +181,9 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
}
return (
-