mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-02 18:25:45 +00:00
Remove/deprecate service accounts + old logs/actions
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
faAngleDown,
|
||||
faEye,
|
||||
faPlus,
|
||||
faShuffle,
|
||||
faTrash,
|
||||
faX
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
|
||||
interface ListBoxProps {
|
||||
selected: string;
|
||||
select: (event: string) => void;
|
||||
}
|
||||
|
||||
const eventOptions = [
|
||||
{
|
||||
name: "addSecrets",
|
||||
icon: faPlus
|
||||
},
|
||||
{
|
||||
name: "readSecrets",
|
||||
icon: faEye
|
||||
},
|
||||
{
|
||||
name: "updateSecrets",
|
||||
icon: faShuffle
|
||||
},
|
||||
{
|
||||
name: "deleteSecrets",
|
||||
icon: faTrash
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* This is the component that we use for the event picker in the activity logs tab.
|
||||
* @param {object} obj
|
||||
* @param {string} obj.selected - the event that is currently selected
|
||||
* @param {function} obj.select - an action that happens when an item is selected
|
||||
*/
|
||||
const EventFilter = ({ selected, select }: ListBoxProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Listbox value={t(`activity.event.${selected}`)} onChange={select}>
|
||||
<div className="relative">
|
||||
<Listbox.Button className="flex h-10 w-52 cursor-pointer items-center justify-between rounded-md bg-mineshaft-800 pl-4 pr-2 text-sm text-bunker-200 duration-200 hover:bg-mineshaft-700">
|
||||
{selected !== "" ? (
|
||||
<p className="select-none text-bunker-100">{t(`activity.event.${selected}`)}</p>
|
||||
) : (
|
||||
<p className="select-none">{String(t("common.select-event"))}</p>
|
||||
)}
|
||||
{selected !== "" ? (
|
||||
<FontAwesomeIcon icon={faX} className="w-2 p-2 pl-2" onClick={() => select("")} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faAngleDown} className="pl-4 pr-2" />
|
||||
)}
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-50 mt-1 max-h-60 w-52 overflow-auto rounded-md border border-mineshaft-700 bg-bunker p-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
{eventOptions.map((event, id) => (
|
||||
<Listbox.Option
|
||||
key={`${event.name}.${id + 1}`}
|
||||
className={`flex h-10 cursor-pointer items-center rounded-md px-4 text-sm text-bunker-200 hover:bg-mineshaft-700 ${
|
||||
selected === t(`activity.event.${event.name}`) && "bg-mineshaft-700"
|
||||
}`}
|
||||
value={event.name}
|
||||
>
|
||||
{({ selected: isSelected }) => (
|
||||
<span
|
||||
className={`block truncate ${isSelected ? "font-semibold" : "font-normal"}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={event.icon} className="pr-4" />{" "}
|
||||
{t(`activity.event.${event.name}`)}
|
||||
</span>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventFilter;
|
||||
@@ -1,76 +0,0 @@
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
|
||||
interface WorkspaceProps {
|
||||
workspaceId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
userId: string;
|
||||
actionNames: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fetches the activity logs for a certain project
|
||||
* @param {object} obj
|
||||
* @param {string} obj.workspaceId - workspace id for which we are trying to get project log
|
||||
* @param {object} obj.offset - teh starting point of logs that we want to pull
|
||||
* @param {object} obj.limit - how many logs will we output
|
||||
* @param {object} obj.userId - optional userId filter - will only query logs for that user
|
||||
* @param {string} obj.actionNames - optional actionNames filter - will only query logs for those actions
|
||||
* @returns
|
||||
*/
|
||||
const getProjectLogs = async ({
|
||||
workspaceId,
|
||||
offset,
|
||||
limit,
|
||||
userId,
|
||||
actionNames
|
||||
}: WorkspaceProps) => {
|
||||
let payload;
|
||||
if (userId !== "" && actionNames !== "") {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: "recent",
|
||||
userId: JSON.stringify(userId),
|
||||
actionNames
|
||||
};
|
||||
} else if (userId !== "") {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: "recent",
|
||||
userId: JSON.stringify(userId)
|
||||
};
|
||||
} else if (actionNames !== "") {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: "recent",
|
||||
actionNames
|
||||
};
|
||||
} else {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: "recent"
|
||||
};
|
||||
}
|
||||
|
||||
return SecurityClient.fetchCall(
|
||||
`/api/v1/workspace/${workspaceId}/logs?${new URLSearchParams(payload)}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).logs;
|
||||
}
|
||||
console.log("Failed to get project logs");
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default getProjectLogs;
|
||||
@@ -1,237 +0,0 @@
|
||||
// TODO: deprecate in favor of new audit logs
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import getActionData from "@app/ee/api/secrets/GetActionData";
|
||||
import patienceDiff from "@app/ee/utilities/findTextDifferences";
|
||||
import {
|
||||
useGetUserWsKey
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric
|
||||
} from "../../components/utilities/cryptography/crypto";
|
||||
|
||||
interface SideBarProps {
|
||||
toggleSidebar: (value: string) => void;
|
||||
currentAction: string;
|
||||
}
|
||||
|
||||
interface SecretProps {
|
||||
secret: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyHash: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueHash: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
}
|
||||
|
||||
interface DecryptedSecretProps {
|
||||
newSecretVersion: {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
oldSecretVersion: {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ActionProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} obj
|
||||
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
|
||||
* @param {string} obj.currentAction - the action id for which a sidebar is being displayed
|
||||
* @returns the sidebar with the payload of user activity logs
|
||||
*/
|
||||
const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
|
||||
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { data: wsKey } = useGetUserWsKey(String(router.query.id));
|
||||
|
||||
useEffect(() => {
|
||||
const getLogData = async () => {
|
||||
setIsLoading(true);
|
||||
const tempActionData = await getActionData({ actionId: currentAction });
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
// #TODO: make this a separate function and reuse across the app
|
||||
let decryptedLatestKey: string;
|
||||
if (wsKey) {
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
decryptedLatestKey = decryptAssymmetric({
|
||||
ciphertext: wsKey.encryptedKey,
|
||||
nonce: wsKey.nonce,
|
||||
publicKey: wsKey.sender.publicKey,
|
||||
privateKey: String(PRIVATE_KEY)
|
||||
});
|
||||
|
||||
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
|
||||
(encryptedSecretVersion: {
|
||||
newSecretVersion?: SecretProps;
|
||||
oldSecretVersion?: SecretProps;
|
||||
}) => ({
|
||||
newSecretVersion: {
|
||||
key: decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.newSecretVersion!.secretKeyCiphertext,
|
||||
iv: encryptedSecretVersion.newSecretVersion!.secretKeyIV,
|
||||
tag: encryptedSecretVersion.newSecretVersion!.secretKeyTag,
|
||||
key: decryptedLatestKey
|
||||
}),
|
||||
value: decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.newSecretVersion!.secretValueCiphertext,
|
||||
iv: encryptedSecretVersion.newSecretVersion!.secretValueIV,
|
||||
tag: encryptedSecretVersion.newSecretVersion!.secretValueTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
},
|
||||
oldSecretVersion: {
|
||||
key: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext
|
||||
? decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext,
|
||||
iv: encryptedSecretVersion.oldSecretVersion?.secretKeyIV,
|
||||
tag: encryptedSecretVersion.oldSecretVersion?.secretKeyTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
: undefined,
|
||||
value: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext
|
||||
? decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext,
|
||||
iv: encryptedSecretVersion.oldSecretVersion?.secretValueIV,
|
||||
tag: encryptedSecretVersion.oldSecretVersion?.secretValueTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
setActionData(decryptedSecretVersions);
|
||||
setActionMetaData({ name: tempActionData.name });
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
getLogData();
|
||||
}, [currentAction, wsKey]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute border-l border-mineshaft-500 ${
|
||||
isLoading ? "bg-bunker-800" : "bg-bunker"
|
||||
} fixed right-0 z-40 flex h-[calc(100vh)] w-96 flex-col justify-between shadow-xl`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="mb-8 flex h-full items-center justify-center">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-min">
|
||||
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
|
||||
<p className="text-lg font-semibold text-bunker-200">
|
||||
{t(`activity.event.${actionMetaData?.name}`)}
|
||||
</p>
|
||||
<div
|
||||
className="p-1"
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => toggleSidebar("")}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-y-autp flex h-[calc(100vh-120px)] flex-col overflow-y-auto px-4">
|
||||
{(actionMetaData?.name === "readSecrets" ||
|
||||
actionMetaData?.name === "addSecrets" ||
|
||||
actionMetaData?.name === "deleteSecrets") &&
|
||||
actionData?.map((item, id) => (
|
||||
<div key={`secret.${id + 1}`}>
|
||||
<div className="ph-no-capture mt-4 pl-1 text-xs text-bunker-200">
|
||||
{item.newSecretVersion.key}
|
||||
</div>
|
||||
<div className="w-full break-all rounded-md border border-mineshaft-500 bg-mineshaft-600 px-2 py-0.5 font-mono text-sm text-bunker-200">
|
||||
{item.newSecretVersion.value ? (
|
||||
<span> {item.newSecretVersion.value} </span>
|
||||
) : (
|
||||
<span className="text-bunker-400"> EMPTY </span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{actionMetaData?.name === "updateSecrets" &&
|
||||
actionData?.map((item, id) => (
|
||||
<>
|
||||
<div className="mt-4 pl-1 text-xs text-bunker-200">
|
||||
{item.newSecretVersion.key}
|
||||
</div>
|
||||
<div className="overflow-hidden break-all rounded-md border border-mineshaft-500 font-mono text-bunker-200">
|
||||
<div className="ph-no-capture bg-red/40 px-2">
|
||||
-{" "}
|
||||
{patienceDiff(
|
||||
item.oldSecretVersion.value.split(""),
|
||||
item.newSecretVersion.value.split(""),
|
||||
false
|
||||
).lines.map(
|
||||
(character, lineId) =>
|
||||
character.bIndex !== -1 && (
|
||||
<span
|
||||
key={`actionData.${id + 1}.line.${lineId + 1}`}
|
||||
className={`${
|
||||
character.aIndex === -1 && "bg-red-700/80 text-bunker-100"
|
||||
}`}
|
||||
>
|
||||
{character.line}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="ph-no-capture break-all bg-green-500/40 px-2">
|
||||
+{" "}
|
||||
{patienceDiff(
|
||||
item.oldSecretVersion.value.split(""),
|
||||
item.newSecretVersion.value.split(""),
|
||||
false
|
||||
).lines.map(
|
||||
(character, lineId) =>
|
||||
character.aIndex !== -1 && (
|
||||
<span
|
||||
key={`actionData.${id + 1}.linev2.${lineId + 1}`}
|
||||
className={`${
|
||||
character.bIndex === -1 && "bg-green-700/80 text-bunker-100"
|
||||
}`}
|
||||
>
|
||||
{character.line}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivitySideBar;
|
||||
@@ -1,205 +0,0 @@
|
||||
// TODO: deprecate in favor of new audit logs
|
||||
|
||||
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faAngleDown, faAngleRight, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import timeSince from "@app/ee/utilities/timeSince";
|
||||
|
||||
import guidGenerator from "../../components/utilities/randomId";
|
||||
|
||||
interface PayloadProps {
|
||||
_id: string;
|
||||
name: string;
|
||||
secretVersions: string[];
|
||||
}
|
||||
|
||||
interface LogData {
|
||||
_id: string;
|
||||
channel: string;
|
||||
createdAt: string;
|
||||
ipAddress: string;
|
||||
user: string;
|
||||
serviceAccount: {
|
||||
name: string;
|
||||
};
|
||||
serviceTokenData: {
|
||||
name: string;
|
||||
};
|
||||
payload: PayloadProps[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a single row of the activity table
|
||||
* @param obj
|
||||
* @param {LogData} obj.row - data for a certain event
|
||||
* @param {function} obj.toggleSidebar - open and close sidebar that displays data for a specific event
|
||||
* @returns
|
||||
*/
|
||||
const ActivityLogsRow = ({
|
||||
row,
|
||||
toggleSidebar
|
||||
}: {
|
||||
row: LogData;
|
||||
toggleSidebar: (value: string) => void;
|
||||
}) => {
|
||||
const [payloadOpened, setPayloadOpened] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const renderUser = () => {
|
||||
|
||||
if (row?.user) return `${row.user}`;
|
||||
if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`;
|
||||
if (row?.serviceTokenData?.name) return `Service Token: ${row.serviceTokenData.name}`;
|
||||
|
||||
return "";
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div key={guidGenerator()} className="w-full bg-mineshaft-800 text-sm text-mineshaft-200 duration-100 flex flex-row items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPayloadOpened(!payloadOpened)}
|
||||
className="border-t border-mineshaft-700 pt-[0.58rem]"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={payloadOpened ? faAngleDown : faAngleRight}
|
||||
className={`ml-6 mb-2 text-mineshaft-300 cursor-pointer ${
|
||||
payloadOpened ? "bg-mineshaft-500 hover:bg-mineshaft-500" : "hover:bg-mineshaft-700"
|
||||
} h-4 w-4 rounded-md p-1 duration-100`}
|
||||
/>
|
||||
</button>
|
||||
<div className="border-t border-mineshaft-700 py-3 w-1/4 pl-6">
|
||||
{row.payload
|
||||
?.map(
|
||||
(action) =>
|
||||
`${String(action.secretVersions.length)} ${t(`activity.event.${action.name}`)}`
|
||||
)
|
||||
.join(" and ")}
|
||||
</div>
|
||||
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">{renderUser()}</div>
|
||||
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">{row.channel}</div>
|
||||
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">
|
||||
{timeSince(new Date(row.createdAt))}
|
||||
</div>
|
||||
</div>
|
||||
{payloadOpened && (
|
||||
<div className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center">
|
||||
<div className='max-w-xl w-full flex flex-row items-center'>
|
||||
<div className='w-24' />
|
||||
<div className='w-1/2'>{String(t("common.timestamp"))}</div>
|
||||
<div className='w-1/2'>{row.createdAt}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{payloadOpened &&
|
||||
row.payload?.map(
|
||||
(action) =>
|
||||
action.secretVersions.length > 0 && (
|
||||
<div
|
||||
key={action.name}
|
||||
className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center"
|
||||
>
|
||||
<div className='max-w-xl w-full flex flex-row items-center'>
|
||||
<div className='w-24' />
|
||||
<div className='w-1/2'>{t(`activity.event.${action.name}`)}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleSidebar(action._id)}
|
||||
className='w-1/2 text-primary-300 hover:text-primary-500 flex flex-row justify-left items-center duration-100'
|
||||
>
|
||||
{action.secretVersions.length +
|
||||
(action.secretVersions.length !== 1 ? " secrets" : " secret")}
|
||||
<FontAwesomeIcon
|
||||
icon={faUpRightFromSquare}
|
||||
className="ml-2 mb-0.5 h-3 w-3 font-light"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{payloadOpened && (
|
||||
<div className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center">
|
||||
<div className='max-w-xl w-full flex flex-row items-center'>
|
||||
<div className='w-24' />
|
||||
<div className='w-1/2'>{String(t("activity.ip-address"))}</div>
|
||||
<div className='w-1/2'>{row.ipAddress}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the table for activity logs (one of the tabs)
|
||||
* @param {object} obj
|
||||
* @param {logData} obj.data - data for user activity logs
|
||||
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
|
||||
* @param {boolean} obj.isLoading - whether the log data has been loaded yet or not
|
||||
* @returns
|
||||
*/
|
||||
const ActivityTable = ({
|
||||
data,
|
||||
toggleSidebar,
|
||||
isLoading
|
||||
}: {
|
||||
data: LogData[];
|
||||
toggleSidebar: (value: string) => void;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="mt-8 w-full px-6">
|
||||
<div className="table-container relative mb-6 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800">
|
||||
{/* <div className="absolute h-[3rem] w-full rounded-t-md bg-white/5" /> */}
|
||||
<div className="my-1 w-full">
|
||||
<div className="text-bunker-300 border-b border-mineshaft-600">
|
||||
<div className="text-sm flex flex-row w-full">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {}}
|
||||
className="opacity-0"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleRight}
|
||||
className="ml-6 mb-2 text-bunker-100 hover:bg-mineshaft-700 cursor-pointer h-4 w-4 rounded-md p-1 duration-100"
|
||||
/>
|
||||
</button>
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||
{String(t("common.event")).toUpperCase()}
|
||||
</div>
|
||||
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||
{String(t("common.user")).toUpperCase()}
|
||||
</div>
|
||||
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||
{String(t("common.source")).toUpperCase()}
|
||||
</div>
|
||||
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
|
||||
{String(t("common.time")).toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{data?.map((row, index) => (
|
||||
<ActivityLogsRow
|
||||
key={`activity.${index + 1}.${row._id}`}
|
||||
row={row}
|
||||
toggleSidebar={toggleSidebar}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className="mb-8 mt-4 bg-mineshaft-800 rounded-md h-60 flex w-full justify-center animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivityTable;
|
||||
@@ -15,7 +15,6 @@ export * from "./secretImports";
|
||||
export * from "./secretRotation";
|
||||
export * from "./secrets";
|
||||
export * from "./secretSnapshots";
|
||||
export * from "./serviceAccounts";
|
||||
export * from "./serviceTokens";
|
||||
export * from "./ssoConfig";
|
||||
export * from "./subscriptions";
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export {
|
||||
useCreateServiceAccount,
|
||||
useCreateServiceAccountProjectLevelPermission,
|
||||
useDeleteServiceAccount,
|
||||
useDeleteServiceAccountProjectLevelPermission,
|
||||
useGetServiceAccountById,
|
||||
useGetServiceAccountProjectLevelPermissions,
|
||||
useGetServiceAccounts,
|
||||
useRenameServiceAccount
|
||||
} from "./queries";
|
||||
@@ -1,137 +0,0 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import {
|
||||
CreateServiceAccountDTO,
|
||||
CreateServiceAccountRes,
|
||||
CreateServiceAccountWorkspacePermissionDTO,
|
||||
DeleteServiceAccountWorkspacePermissionDTO,
|
||||
RenameServiceAccountDTO,
|
||||
ServiceAccount,
|
||||
ServiceAccountWorkspacePermission
|
||||
} from "./types";
|
||||
|
||||
const serviceAccountKeys = {
|
||||
getServiceAccountById: (serviceAccountId: string) => [{ serviceAccountId }, "service-account"] as const,
|
||||
getServiceAccounts: (organizationID: string) => [{ organizationID }, "service-accounts"] as const,
|
||||
getServiceAccountProjectLevelPermissions: (serviceAccountId: string) => [{ serviceAccountId }, "service-account-project-level-permissions"] as const
|
||||
}
|
||||
|
||||
const fetchServiceAccounts = async (organizationID: string) => {
|
||||
const { data } = await apiRequest.get<{ serviceAccounts: ServiceAccount[] }>(
|
||||
`/api/v2/organizations/${organizationID}/service-accounts`
|
||||
);
|
||||
|
||||
return data.serviceAccounts;
|
||||
}
|
||||
|
||||
const fetchServiceAccountById = async (serviceAccountId: string) => {
|
||||
const { data } = await apiRequest.get<{ serviceAccount: ServiceAccount }>(
|
||||
`/api/v2/service-accounts/${serviceAccountId}`
|
||||
);
|
||||
|
||||
return data.serviceAccount;
|
||||
}
|
||||
|
||||
export const useGetServiceAccounts = (organizationID: string) =>
|
||||
useQuery({
|
||||
queryKey: serviceAccountKeys.getServiceAccounts(organizationID),
|
||||
queryFn: () => fetchServiceAccounts(organizationID),
|
||||
enabled: Boolean(organizationID)
|
||||
});
|
||||
|
||||
export const useGetServiceAccountById = (serviceAccountId: string) => {
|
||||
return useQuery({
|
||||
queryKey: serviceAccountKeys.getServiceAccountById(serviceAccountId),
|
||||
queryFn: () => fetchServiceAccountById(serviceAccountId),
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
|
||||
export const useCreateServiceAccount = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<CreateServiceAccountRes, {}, CreateServiceAccountDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { data } = await apiRequest.post("/api/v2/service-accounts/", body);
|
||||
return data;
|
||||
},
|
||||
onSuccess: ({ serviceAccount }) => {
|
||||
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccounts(serviceAccount.organization));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const useRenameServiceAccount = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ServiceAccount, {}, RenameServiceAccountDTO>({
|
||||
mutationFn: async ({ serviceAccountId, name }) => {
|
||||
const { data: { serviceAccount } } = await apiRequest.patch(`/api/v2/service-accounts/${serviceAccountId}/name`, { name });
|
||||
return serviceAccount;
|
||||
},
|
||||
onSuccess: (serviceAccount) => {
|
||||
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountById(serviceAccount._id));
|
||||
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccounts(serviceAccount.organization));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const useDeleteServiceAccount = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ServiceAccount, {}, string>({
|
||||
mutationFn: async (serviceAccountId) => {
|
||||
const { data: { serviceAccount } } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}`);
|
||||
return serviceAccount;
|
||||
},
|
||||
onSuccess: ({ organization }) => {
|
||||
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccounts(organization));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fetchServiceAccountProjectLevelPermissions = async (serviceAccountId: string) => {
|
||||
const { data: { serviceAccountWorkspacePermissions } } = await apiRequest.get<{ serviceAccountWorkspacePermissions: ServiceAccountWorkspacePermission[] }>(
|
||||
`/api/v2/service-accounts/${serviceAccountId}/permissions/workspace`
|
||||
);
|
||||
|
||||
return serviceAccountWorkspacePermissions;
|
||||
}
|
||||
|
||||
export const useGetServiceAccountProjectLevelPermissions = (serviceAccountId: string) => {
|
||||
return useQuery({
|
||||
queryKey: serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccountId),
|
||||
queryFn: () => fetchServiceAccountProjectLevelPermissions(serviceAccountId),
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
|
||||
export const useCreateServiceAccountProjectLevelPermission = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ServiceAccountWorkspacePermission, {}, CreateServiceAccountWorkspacePermissionDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { data: { serviceAccountWorkspacePermission } } = await apiRequest.post(`/api/v2/service-accounts/${body.serviceAccountId}/permissions/workspace`, body);
|
||||
return serviceAccountWorkspacePermission;
|
||||
},
|
||||
onSuccess: ({ serviceAccount }) => {
|
||||
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccount));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const useDeleteServiceAccountProjectLevelPermission = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ServiceAccountWorkspacePermission, {}, DeleteServiceAccountWorkspacePermissionDTO>({
|
||||
mutationFn: async ({ serviceAccountId, serviceAccountWorkspacePermissionId }) => {
|
||||
const { data: { serviceAccountWorkspacePermission} } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}/permissions/workspace/${serviceAccountWorkspacePermissionId}`);
|
||||
return serviceAccountWorkspacePermission;
|
||||
},
|
||||
onSuccess: (serviceAccountWorkspacePermission) => {
|
||||
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccountWorkspacePermission.serviceAccount));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Workspace } from "../workspace/types";
|
||||
|
||||
export type ServiceAccount = {
|
||||
_id: string;
|
||||
name: string;
|
||||
organization: string;
|
||||
user: string;
|
||||
publicKey: string;
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
export type CreateServiceAccountDTO = {
|
||||
name: string;
|
||||
organizationId: string;
|
||||
publicKey: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
export type CreateServiceAccountRes = {
|
||||
serviceAccount: ServiceAccount;
|
||||
serviceAccountAccessKey: string;
|
||||
}
|
||||
|
||||
export type RenameServiceAccountDTO = {
|
||||
serviceAccountId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type ServiceAccountWorkspacePermission = {
|
||||
_id: string;
|
||||
serviceAccount: string;
|
||||
workspace: Workspace;
|
||||
environment: string;
|
||||
read: boolean;
|
||||
write: boolean;
|
||||
}
|
||||
|
||||
export type CreateServiceAccountWorkspacePermissionDTO = {
|
||||
serviceAccountId: string;
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
read: boolean;
|
||||
write: boolean;
|
||||
encryptedKey: string;
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
export type DeleteServiceAccountWorkspacePermissionDTO = {
|
||||
serviceAccountId: string;
|
||||
serviceAccountWorkspacePermissionId: string;
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import Button from "@app/components/basic/buttons/Button";
|
||||
import EventFilter from "@app/components/basic/EventFilter";
|
||||
import { UpgradePlanModal } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from "@app/context";
|
||||
import ActivitySideBar from "@app/ee/components/ActivitySideBar";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import getProjectLogs from "../../../../ee/api/secrets/GetProjectLogs";
|
||||
import ActivityTable from "../../../../ee/components/ActivityTable";
|
||||
|
||||
interface LogData {
|
||||
_id: string;
|
||||
channel: string;
|
||||
createdAt: string;
|
||||
ipAddress: string;
|
||||
user: {
|
||||
email: string;
|
||||
};
|
||||
serviceAccount?: {
|
||||
string: string;
|
||||
};
|
||||
serviceTokenData?: {
|
||||
name: string;
|
||||
};
|
||||
actions: {
|
||||
_id: string;
|
||||
name: string;
|
||||
payload: {
|
||||
secretVersions: string[];
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
interface PayloadProps {
|
||||
_id: string;
|
||||
name: string;
|
||||
secretVersions: string[];
|
||||
}
|
||||
|
||||
interface LogDataPoint {
|
||||
_id: string;
|
||||
channel: string;
|
||||
createdAt: string;
|
||||
ipAddress: string;
|
||||
user: string;
|
||||
serviceAccount: {
|
||||
name: string;
|
||||
};
|
||||
serviceTokenData: {
|
||||
name: string;
|
||||
};
|
||||
payload: PayloadProps[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the tab that includes all of the user activity logs
|
||||
*/
|
||||
const Activity = withProjectPermission(
|
||||
() => {
|
||||
const router = useRouter();
|
||||
const [eventChosen, setEventChosen] = useState("");
|
||||
const [logsData, setLogsData] = useState<LogDataPoint[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [currentOffset, setCurrentOffset] = useState(0);
|
||||
const currentLimit = 10;
|
||||
const [currentSidebarAction, toggleSidebar] = useState<string>();
|
||||
const { t } = useTranslation();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
// this use effect updates the data in case of a new filter being added
|
||||
useEffect(() => {
|
||||
setCurrentOffset(0);
|
||||
const getLogData = async () => {
|
||||
setIsLoading(true);
|
||||
const tempLogsData = await getProjectLogs({
|
||||
workspaceId: String(router.query.id),
|
||||
offset: 0,
|
||||
limit: currentLimit,
|
||||
userId: "",
|
||||
actionNames: eventChosen
|
||||
});
|
||||
|
||||
setLogsData(
|
||||
tempLogsData.map((log: LogData) => ({
|
||||
_id: log._id,
|
||||
channel: log.channel,
|
||||
createdAt: log.createdAt,
|
||||
ipAddress: log.ipAddress,
|
||||
user: log?.user?.email,
|
||||
serviceAccount: log?.serviceAccount,
|
||||
serviceTokenData: log?.serviceTokenData,
|
||||
payload: log.actions.map((action) => ({
|
||||
_id: action._id,
|
||||
name: action.name,
|
||||
secretVersions: action.payload.secretVersions
|
||||
}))
|
||||
}))
|
||||
);
|
||||
setIsLoading(false);
|
||||
};
|
||||
getLogData();
|
||||
}, [eventChosen]);
|
||||
|
||||
// this use effect adds more data in case 'View More' button is clicked
|
||||
useEffect(() => {
|
||||
const getLogData = async () => {
|
||||
setIsLoading(true);
|
||||
const tempLogsData = await getProjectLogs({
|
||||
workspaceId: String(router.query.id),
|
||||
offset: currentOffset,
|
||||
limit: currentLimit,
|
||||
userId: "",
|
||||
actionNames: eventChosen
|
||||
});
|
||||
setLogsData(
|
||||
logsData.concat(
|
||||
tempLogsData.map((log: LogData) => ({
|
||||
_id: log._id,
|
||||
channel: log.channel,
|
||||
createdAt: log.createdAt,
|
||||
ipAddress: log.ipAddress,
|
||||
user: log?.user?.email,
|
||||
serviceAccount: log?.serviceAccount,
|
||||
serviceTokenData: log?.serviceTokenData,
|
||||
payload: log.actions.map((action) => ({
|
||||
_id: action._id,
|
||||
name: action.name,
|
||||
secretVersions: action.payload.secretVersions
|
||||
}))
|
||||
}))
|
||||
)
|
||||
);
|
||||
setIsLoading(false);
|
||||
};
|
||||
getLogData();
|
||||
}, [currentLimit, currentOffset]);
|
||||
|
||||
const loadMoreLogs = () => {
|
||||
if (subscription?.auditLogs === false) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
} else {
|
||||
setCurrentOffset(currentOffset + currentLimit);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full h-full max-w-7xl">
|
||||
<Head>
|
||||
<title>Audit Logs</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Head>
|
||||
{currentSidebarAction && (
|
||||
<ActivitySideBar toggleSidebar={toggleSidebar} currentAction={currentSidebarAction} />
|
||||
)}
|
||||
<div className="flex flex-col justify-between items-start mx-4 mb-4 text-xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl mt-6">
|
||||
<p className="font-semibold mr-4 text-bunker-100">{t("activity.title")}</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">{t("activity.subtitle")}</p>
|
||||
</div>
|
||||
<div className="px-6 h-8 mt-2">
|
||||
<EventFilter selected={eventChosen} select={setEventChosen} />
|
||||
</div>
|
||||
<ActivityTable data={logsData} toggleSidebar={toggleSidebar} isLoading={isLoading} />
|
||||
<div className="flex justify-center w-full mb-6">
|
||||
<div className="items-center w-60">
|
||||
<Button
|
||||
text={String(t("common.view-more"))}
|
||||
textDisabled={String(t("common.end-of-history"))}
|
||||
active={logsData.length % 10 === 0}
|
||||
onButtonPressed={loadMoreLogs}
|
||||
size="md"
|
||||
color="mineshaft"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{subscription && (
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={() => handlePopUpClose("upgradePlan")}
|
||||
text={
|
||||
subscription.slug === null
|
||||
? "You can see more logs under an Enterprise license"
|
||||
: "You can see more logs if you switch to Infisical's Business/Professional Plan."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.AuditLogs }
|
||||
);
|
||||
|
||||
Object.assign(Activity, { requireAuth: true });
|
||||
|
||||
export default Activity;
|
||||
-389
@@ -1,389 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faCheck,
|
||||
faCopy,
|
||||
faMagnifyingGlass,
|
||||
faPencil,
|
||||
faPlus,
|
||||
faServer,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
// import { yupResolver } from "@hookform/resolvers/yup";
|
||||
// import * as yup from "yup";
|
||||
// import { generateKeyPair } from "@app/components/utilities/cryptography/crypto";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
// FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
// Select,
|
||||
// SelectItem,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import {
|
||||
// useCreateServiceAccount,
|
||||
useDeleteServiceAccount,
|
||||
useGetServiceAccounts
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import // Controller,
|
||||
// useForm
|
||||
"react-hook-form";
|
||||
|
||||
// const serviceAccountExpiration = [
|
||||
// { label: "1 Day", value: 86400 },
|
||||
// { label: "7 Days", value: 604800 },
|
||||
// { label: "1 Month", value: 2592000 },
|
||||
// { label: "6 months", value: 15552000 },
|
||||
// { label: "12 months", value: 31104000 },
|
||||
// { label: "Never", value: -1 }
|
||||
// ];
|
||||
|
||||
// const addServiceAccountFormSchema = yup.object({
|
||||
// name: yup.string().required().label("Name").trim(),
|
||||
// expiresIn: yup.string().required().label("Service Account Expiration")
|
||||
// });
|
||||
|
||||
// type TAddServiceAccountForm = yup.InferType<typeof addServiceAccountFormSchema>;
|
||||
|
||||
export const OrgServiceAccountsTable = withPermission(
|
||||
() => {
|
||||
const router = useRouter();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const orgId = currentOrg?._id || "";
|
||||
const [step, setStep] = useState(0);
|
||||
const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false);
|
||||
const [isPublicKeyCopied, setIsPublicKeyCopied] = useToggle(false);
|
||||
const [isPrivateKeyCopied, setIsPrivateKeyCopied] = useToggle(false);
|
||||
const [accessKey] = useState("");
|
||||
const [publicKey] = useState("");
|
||||
const [privateKey] = useState("");
|
||||
const [searchServiceAccountFilter, setSearchServiceAccountFilter] = useState("");
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"addServiceAccount",
|
||||
"removeServiceAccount"
|
||||
] as const);
|
||||
|
||||
const { data: serviceAccounts = [], isLoading: isServiceAccountsLoading } =
|
||||
useGetServiceAccounts(orgId);
|
||||
|
||||
// const createServiceAccount = useCreateServiceAccount();
|
||||
const removeServiceAccount = useDeleteServiceAccount();
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
if (isAccessKeyCopied) {
|
||||
timer = setTimeout(() => setIsAccessKeyCopied.off(), 2000);
|
||||
}
|
||||
|
||||
if (isPublicKeyCopied) {
|
||||
timer = setTimeout(() => setIsPublicKeyCopied.off(), 2000);
|
||||
}
|
||||
|
||||
if (isPrivateKeyCopied) {
|
||||
timer = setTimeout(() => setIsPrivateKeyCopied.off(), 2000);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isAccessKeyCopied, isPublicKeyCopied, isPrivateKeyCopied]);
|
||||
|
||||
// const {
|
||||
// control,
|
||||
// handleSubmit,
|
||||
// reset,
|
||||
// formState: { isSubmitting }
|
||||
// } = useForm<TAddServiceAccountForm>({ resolver: yupResolver(addServiceAccountFormSchema) });
|
||||
|
||||
// const onAddServiceAccount = async ({ name, expiresIn }: TAddServiceAccountForm) => {
|
||||
// if (!currentOrg?._id) return;
|
||||
|
||||
// const keyPair = generateKeyPair();
|
||||
// setPublicKey(keyPair.publicKey);
|
||||
// setPrivateKey(keyPair.privateKey);
|
||||
|
||||
// const serviceAccountDetails = await createServiceAccount.mutateAsync({
|
||||
// name,
|
||||
// organizationId: currentOrg?._id,
|
||||
// publicKey: keyPair.publicKey,
|
||||
// expiresIn: Number(expiresIn)
|
||||
// });
|
||||
|
||||
// setAccessKey(serviceAccountDetails.serviceAccountAccessKey);
|
||||
|
||||
// setStep(1);
|
||||
// reset();
|
||||
// }
|
||||
|
||||
const onRemoveServiceAccount = async () => {
|
||||
const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id;
|
||||
await removeServiceAccount.mutateAsync(serviceAccountId);
|
||||
handlePopUpClose("removeServiceAccount");
|
||||
};
|
||||
|
||||
const filteredServiceAccounts = useMemo(
|
||||
() =>
|
||||
serviceAccounts.filter(({ name }) =>
|
||||
name.toLowerCase().includes(searchServiceAccountFilter)
|
||||
),
|
||||
[serviceAccounts, searchServiceAccountFilter]
|
||||
);
|
||||
|
||||
const renderStep = (stepToRender: number) => {
|
||||
switch (stepToRender) {
|
||||
case 0:
|
||||
return (
|
||||
<div>
|
||||
We are currently revising the service account mechanism. In the meantime, please use
|
||||
service tokens or API key to fetch secrets via API request.
|
||||
</div>
|
||||
// <form onSubmit={handleSubmit(onAddServiceAccount)}>
|
||||
// <Controller
|
||||
// control={control}
|
||||
// defaultValue=""
|
||||
// name="name"
|
||||
// render={({ field, fieldState: { error } }) => (
|
||||
// <FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
|
||||
// <Input {...field} />
|
||||
// </FormControl>
|
||||
// )}
|
||||
// />
|
||||
// <Controller
|
||||
// control={control}
|
||||
// name="expiresIn"
|
||||
// defaultValue={String(serviceAccountExpiration?.[0]?.value)}
|
||||
// render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
// return (
|
||||
// <FormControl
|
||||
// label="Expiration"
|
||||
// errorText={error?.message}
|
||||
// isError={Boolean(error)}
|
||||
// >
|
||||
// <Select
|
||||
// defaultValue={field.value}
|
||||
// {...field}
|
||||
// onValueChange={(e) => onChange(e)}
|
||||
// className="w-full"
|
||||
// >
|
||||
// {serviceAccountExpiration.map(({ label, value }) => (
|
||||
// <SelectItem value={String(value)} key={label}>
|
||||
// {label}
|
||||
// </SelectItem>
|
||||
// ))}
|
||||
// </Select>
|
||||
// </FormControl>
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
// <div className="mt-8 flex items-center">
|
||||
// <Button
|
||||
// className="mr-4"
|
||||
// size="sm"
|
||||
// type="submit"
|
||||
// isLoading={isSubmitting}
|
||||
// isDisabled={isSubmitting}
|
||||
// >
|
||||
// Create Service Account
|
||||
// </Button>
|
||||
// <Button
|
||||
// colorSchema="secondary"
|
||||
// variant="plain"
|
||||
// onClick={() => handlePopUpClose("addServiceAccount")}
|
||||
// >
|
||||
// Cancel
|
||||
// </Button>
|
||||
// </div>
|
||||
// </form>
|
||||
);
|
||||
case 1:
|
||||
return (
|
||||
<>
|
||||
<p>Access Key</p>
|
||||
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 break-all">{accessKey}</p>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(accessKey);
|
||||
setIsAccessKeyCopied.on();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isAccessKeyCopied ? faCheck : faCopy} />
|
||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
Copy
|
||||
</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
<p className="mt-4">Public Key</p>
|
||||
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 break-all">{publicKey}</p>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(publicKey);
|
||||
setIsPublicKeyCopied.on();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isPublicKeyCopied ? faCheck : faCopy} />
|
||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
Copy
|
||||
</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
<p className="mt-4">Private Key</p>
|
||||
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 break-all">{privateKey}</p>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(privateKey);
|
||||
setIsPrivateKeyCopied.on();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isPrivateKeyCopied ? faCheck : faCopy} />
|
||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
Copy
|
||||
</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return <div />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Service Accounts</p>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
setStep(0);
|
||||
// reset();
|
||||
handlePopUpOpen("addServiceAccount");
|
||||
}}
|
||||
>
|
||||
Add Service Account
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
value={searchServiceAccountFilter}
|
||||
onChange={(e) => setSearchServiceAccountFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search service accounts..."
|
||||
/>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
<Th>Name</Th>
|
||||
<Th className="w-full">Valid Until</Th>
|
||||
<Th aria-label="actions" />
|
||||
</THead>
|
||||
<TBody>
|
||||
{isServiceAccountsLoading && (
|
||||
<TableSkeleton columns={5} innerKey="org-service-accounts" />
|
||||
)}
|
||||
{!isServiceAccountsLoading &&
|
||||
filteredServiceAccounts.map(({ name, expiresAt, _id: serviceAccountId }) => {
|
||||
return (
|
||||
<Tr key={`org-service-account-${serviceAccountId}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>{new Date(expiresAt).toUTCString()}</Td>
|
||||
<Td>
|
||||
<div className="flex">
|
||||
<IconButton
|
||||
ariaLabel="edit"
|
||||
colorSchema="secondary"
|
||||
onClick={() => {
|
||||
if (currentWorkspace?._id) {
|
||||
router.push(
|
||||
`/settings/org/${currentWorkspace._id}/service-accounts/${serviceAccountId}`
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="mr-2"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
ariaLabel="delete"
|
||||
colorSchema="danger"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("removeServiceAccount", { _id: serviceAccountId })
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isServiceAccountsLoading && filteredServiceAccounts?.length === 0 && (
|
||||
<EmptyState title="No service accounts found" icon={faServer} />
|
||||
)}
|
||||
</TableContainer>
|
||||
<Modal
|
||||
isOpen={popUp?.addServiceAccount?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("addServiceAccount", isOpen);
|
||||
// reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="Add Service Account"
|
||||
subTitle="A service account represents a machine identity such as a VM or application client."
|
||||
>
|
||||
{renderStep(step)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.removeServiceAccount.isOpen}
|
||||
deleteKey="remove"
|
||||
title="Do you want to remove this service account from the org?"
|
||||
onChange={(isOpen) => handlePopUpToggle("removeServiceAccount", isOpen)}
|
||||
onDeleteApproved={onRemoveServiceAccount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: OrgPermissionActions.Read,
|
||||
subject: OrgPermissionSubjects.Settings,
|
||||
containerClassName: "mb-4"
|
||||
}
|
||||
);
|
||||
-1
@@ -1 +0,0 @@
|
||||
export { OrgServiceAccountsTable } from "./OrgServiceAccountsTable";
|
||||
Reference in New Issue
Block a user