mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 07:26:45 +00:00
Added frontend for api-keys
This commit is contained in:
@@ -0,0 +1,264 @@
|
|||||||
|
import { Fragment, useState } from "react";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
|
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
||||||
|
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||||
|
|
||||||
|
import { envMapping } from "../../../public/data/frequentConstants";
|
||||||
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
encryptSymmetric,
|
||||||
|
} from "../../utilities/cryptography/crypto";
|
||||||
|
import Button from "../buttons/Button";
|
||||||
|
import InputField from "../InputField";
|
||||||
|
import ListBox from "../Listbox";
|
||||||
|
|
||||||
|
const expiryMapping = {
|
||||||
|
"1 day": 86400,
|
||||||
|
"7 days": 604800,
|
||||||
|
"1 month": 2592000,
|
||||||
|
"6 months": 15552000,
|
||||||
|
"12 months": 31104000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const AddApiKeyDialog = ({
|
||||||
|
isOpen,
|
||||||
|
closeModal,
|
||||||
|
workspaceId,
|
||||||
|
workspaceName,
|
||||||
|
serviceTokens,
|
||||||
|
setServiceTokens
|
||||||
|
}) => {
|
||||||
|
const [serviceToken, setServiceToken] = useState("");
|
||||||
|
const [serviceTokenName, setServiceTokenName] = useState("");
|
||||||
|
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
|
||||||
|
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
||||||
|
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const generateServiceToken = async () => {
|
||||||
|
const latestFileKey = await getLatestFileKey({ workspaceId });
|
||||||
|
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||||
|
nonce: latestFileKey.latestKey.nonce,
|
||||||
|
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||||
|
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const randomBytes = crypto.randomBytes(16).toString('hex');
|
||||||
|
const {
|
||||||
|
ciphertext,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
} = encryptSymmetric({
|
||||||
|
plaintext: key,
|
||||||
|
key: randomBytes,
|
||||||
|
});
|
||||||
|
|
||||||
|
let newServiceToken = await addServiceToken({
|
||||||
|
name: serviceTokenName,
|
||||||
|
workspaceId,
|
||||||
|
environment: envMapping[serviceTokenEnv],
|
||||||
|
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||||
|
encryptedKey: ciphertext,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
});
|
||||||
|
|
||||||
|
setServiceTokens(serviceTokens.concat([newServiceToken.serviceTokenData]));
|
||||||
|
setServiceToken(newServiceToken.serviceToken + "." + randomBytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
function copyToClipboard() {
|
||||||
|
// Get the text field
|
||||||
|
var copyText = document.getElementById("serviceToken");
|
||||||
|
|
||||||
|
// Select the text field
|
||||||
|
copyText.select();
|
||||||
|
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||||
|
|
||||||
|
// Copy the text inside the text field
|
||||||
|
navigator.clipboard.writeText(copyText.value);
|
||||||
|
|
||||||
|
setServiceTokenCopied(true);
|
||||||
|
setTimeout(() => setServiceTokenCopied(false), 2000);
|
||||||
|
// Alert the copied text
|
||||||
|
// alert("Copied the text: " + copyText.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeAddServiceTokenModal = () => {
|
||||||
|
closeModal();
|
||||||
|
setServiceTokenName("");
|
||||||
|
setServiceToken("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="z-50">
|
||||||
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
|
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
{serviceToken == "" ? (
|
||||||
|
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
|
<Dialog.Title
|
||||||
|
as="h3"
|
||||||
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
|
>
|
||||||
|
{t("section-token:add-dialog.title", {
|
||||||
|
target: workspaceName,
|
||||||
|
})}
|
||||||
|
</Dialog.Title>
|
||||||
|
<div className="mt-2 mb-4">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{t("section-token:add-dialog.description")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-28 mb-2">
|
||||||
|
<InputField
|
||||||
|
label={t("section-token:add-dialog.name")}
|
||||||
|
onChangeHandler={setServiceTokenName}
|
||||||
|
type="varName"
|
||||||
|
value={serviceTokenName}
|
||||||
|
placeholder=""
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-28 mb-2">
|
||||||
|
<ListBox
|
||||||
|
selected={serviceTokenEnv}
|
||||||
|
onChange={setServiceTokenEnv}
|
||||||
|
data={[
|
||||||
|
"Development",
|
||||||
|
"Staging",
|
||||||
|
"Production",
|
||||||
|
"Testing",
|
||||||
|
]}
|
||||||
|
isFull={true}
|
||||||
|
text={`${t("common:environment")}: `}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-28">
|
||||||
|
<ListBox
|
||||||
|
selected={serviceTokenExpiresIn}
|
||||||
|
onChange={setServiceTokenExpiresIn}
|
||||||
|
data={[
|
||||||
|
"1 day",
|
||||||
|
"7 days",
|
||||||
|
"1 month",
|
||||||
|
"6 months",
|
||||||
|
"12 months",
|
||||||
|
]}
|
||||||
|
isFull={true}
|
||||||
|
text={`${t("common:expired-in")}: `}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-max">
|
||||||
|
<div className="mt-6 flex flex-col justify-start w-max">
|
||||||
|
<Button
|
||||||
|
onButtonPressed={() => generateServiceToken()}
|
||||||
|
color="mineshaft"
|
||||||
|
text={t("section-token:add-dialog.add")}
|
||||||
|
textDisabled={t("section-token:add-dialog.add")}
|
||||||
|
size="md"
|
||||||
|
active={serviceTokenName == "" ? false : true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
) : (
|
||||||
|
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
|
<Dialog.Title
|
||||||
|
as="h3"
|
||||||
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
|
>
|
||||||
|
{t("section-token:add-dialog.copy-service-token")}
|
||||||
|
</Dialog.Title>
|
||||||
|
<div className="mt-2 mb-4">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{t(
|
||||||
|
"section-token:add-dialog.copy-service-token-description"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-20">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={serviceToken}
|
||||||
|
id="serviceToken"
|
||||||
|
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
||||||
|
></input>
|
||||||
|
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
||||||
|
{serviceToken}
|
||||||
|
</div>
|
||||||
|
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||||
|
<button
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||||
|
>
|
||||||
|
{serviceTokenCopied ? (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCheck}
|
||||||
|
className="pr-0.5"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCopy} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||||
|
{t("common:click-to-copy")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 flex flex-col justify-start w-max">
|
||||||
|
<Button
|
||||||
|
onButtonPressed={() => closeAddServiceTokenModal()}
|
||||||
|
color="mineshaft"
|
||||||
|
text="Close"
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
)}
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddApiKeyDialog;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { faX } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
|
||||||
|
|
||||||
|
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
|
||||||
|
import { reverseEnvMapping } from '../../../public/data/frequentConstants';
|
||||||
|
import guidGenerator from '../../utilities/randomId';
|
||||||
|
import Button from '../buttons/Button';
|
||||||
|
|
||||||
|
interface TokenProps {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
environment: string;
|
||||||
|
expiresAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServiceTokensProps {
|
||||||
|
data: TokenProps[];
|
||||||
|
setServiceTokens: (value: TokenProps[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the component that we utilize for the api key table
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {any[]} obj.data - current state of the api key table
|
||||||
|
* @param {function} obj.setServiceTokens - updating the state of the api key table
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const ApiKeyTable = ({ data, setServiceTokens }: ServiceTokensProps) => {
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||||
|
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
||||||
|
<table className="w-full my-1">
|
||||||
|
<thead className="text-bunker-300 text-sm font-light">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left pl-6 pt-2.5 pb-2">API KEY NAME</th>
|
||||||
|
<th className="text-left pl-6 pt-2.5 pb-2">ENVIRONMENT</th>
|
||||||
|
<th className="text-left pl-6 pt-2.5 pb-2">VAILD UNTIL</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data?.length > 0 ? (
|
||||||
|
data?.map((row) => {
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={guidGenerator()}
|
||||||
|
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
|
||||||
|
>
|
||||||
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
|
{row.name}
|
||||||
|
</td>
|
||||||
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
|
{reverseEnvMapping[row.environment]}
|
||||||
|
</td>
|
||||||
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
|
{new Date(row.expiresAt).toUTCString()}
|
||||||
|
</td>
|
||||||
|
<td className="py-2 border-mineshaft-700 border-t">
|
||||||
|
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
||||||
|
<Button
|
||||||
|
onButtonPressed={() => {
|
||||||
|
deleteServiceToken({ serviceTokenId: row._id} );
|
||||||
|
setServiceTokens(data.filter(token => token._id != row._id));
|
||||||
|
createNotification({
|
||||||
|
text: `'${row.name}' token has been revoked.`,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
color="red"
|
||||||
|
size="icon-sm"
|
||||||
|
icon={faX}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4} className="text-center pt-7 pb-5 text-bunker-300 text-sm">
|
||||||
|
No API keys yet
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeyTable;
|
||||||
@@ -2,18 +2,20 @@ import { useEffect, useState } from "react";
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faPlus, faX } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import Button from "~/components/basic/buttons/Button";
|
import Button from "~/components/basic/buttons/Button";
|
||||||
import InputField from "~/components/basic/InputField";
|
import InputField from "~/components/basic/InputField";
|
||||||
import ListBox from "~/components/basic/Listbox";
|
import ListBox from "~/components/basic/Listbox";
|
||||||
|
import ApiKeyTable from "~/components/basic/table/ApiKeyTable.tsx";
|
||||||
import NavHeader from "~/components/navigation/NavHeader";
|
import NavHeader from "~/components/navigation/NavHeader";
|
||||||
import changePassword from "~/components/utilities/cryptography/changePassword";
|
import changePassword from "~/components/utilities/cryptography/changePassword";
|
||||||
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
|
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
|
||||||
import passwordCheck from "~/utilities/checks/PasswordCheck";
|
import passwordCheck from "~/utilities/checks/PasswordCheck";
|
||||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||||
|
|
||||||
|
import AddApiKeyDialog from "../../../components/basic/dialog/AddApiKeyDialog";
|
||||||
import getUser from "../../api/user/getUser";
|
import getUser from "../../api/user/getUser";
|
||||||
|
|
||||||
export default function PersonalSettings() {
|
export default function PersonalSettings() {
|
||||||
@@ -29,6 +31,8 @@ export default function PersonalSettings() {
|
|||||||
const [passwordChanged, setPasswordChanged] = useState(false);
|
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||||
|
const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false)
|
||||||
|
const [apiKeys, setApiKeys] = useState([]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -45,6 +49,10 @@ export default function PersonalSettings() {
|
|||||||
setPersonalName(user.firstName + " " + user.lastName);
|
setPersonalName(user.firstName + " " + user.lastName);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const closeAddApiKeyModal = () => {
|
||||||
|
setIsAddApiKeyDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||||
<Head>
|
<Head>
|
||||||
@@ -53,6 +61,13 @@ export default function PersonalSettings() {
|
|||||||
</title>
|
</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
|
<AddApiKeyDialog
|
||||||
|
isOpen={isAddApiKeyDialogOpen}
|
||||||
|
workspaceId={router.query.id}
|
||||||
|
closeModal={closeAddApiKeyModal}
|
||||||
|
serviceTokens={apiKeys}
|
||||||
|
setServiceTokens={setApiKeys}
|
||||||
|
/>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||||
<NavHeader
|
<NavHeader
|
||||||
@@ -70,54 +85,6 @@ export default function PersonalSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||||
<div className="flex flex-col">
|
|
||||||
<div className="min-w-md flex flex-col items-end pb-4">
|
|
||||||
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
|
||||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
|
||||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
|
||||||
Display Name
|
|
||||||
</p>
|
|
||||||
<InputField
|
|
||||||
onChangeHandler={modifyOrgName}
|
|
||||||
type="varName"
|
|
||||||
value={orgName}
|
|
||||||
placeholder=""
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-start w-full">
|
|
||||||
<div
|
|
||||||
className={`flex justify-start max-w-sm mt-4 mb-2 rounded-md bg-gray-800 text-sm ${
|
|
||||||
buttonReady &&
|
|
||||||
"hover:bg-primary hover:text-black hover:text-semibold duration-200 cursor-pointer"
|
|
||||||
} text-gray-400 px-4 py-2.5`}
|
|
||||||
>
|
|
||||||
{buttonReady ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex flex-start justify-center font-medium px-2"
|
|
||||||
onClick={() =>
|
|
||||||
submitChanges(orgName)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Save Changes
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-row items-center jutify-center px-4">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="text-lg mr-3 text-gray-400"
|
|
||||||
icon={faCheck}
|
|
||||||
/>
|
|
||||||
<p className="font-base">
|
|
||||||
Saved
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||||
<p className="text-xl font-semibold self-start">
|
<p className="text-xl font-semibold self-start">
|
||||||
{t("settings-personal:change-language")}
|
{t("settings-personal:change-language")}
|
||||||
@@ -132,6 +99,44 @@ export default function PersonalSettings() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="bg-white/5 rounded-md px-6 pt-4 flex flex-col items-start flex flex-col items-start w-full mt-2 mb-8 pt-2">
|
||||||
|
<div className="flex flex-row justify-between w-full">
|
||||||
|
<div className="flex flex-col w-full">
|
||||||
|
<p className="text-xl font-semibold mb-3">
|
||||||
|
{t("settings-personal:api-keys.title")}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-400">
|
||||||
|
{t("settings-personal:api-keys.description")}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-400 mb-4">
|
||||||
|
Please, make sure you are on the
|
||||||
|
<a
|
||||||
|
className="text-primary underline underline-offset-2 ml-1"
|
||||||
|
href="https://infisical.com/docs/cli/overview"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
latest version of CLI
|
||||||
|
</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-48 mt-2">
|
||||||
|
<Button
|
||||||
|
text={t("settings-personal:api-keys.add-new")}
|
||||||
|
onButtonPressed={() => {
|
||||||
|
setIsAddApiKeyDialogOpen(true);
|
||||||
|
}}
|
||||||
|
color="mineshaft"
|
||||||
|
icon={faPlus}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ApiKeyTable
|
||||||
|
data={apiKeys}
|
||||||
|
setServiceTokens={setApiKeys}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||||
@@ -295,7 +300,7 @@ export default function PersonalSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||||
|
|||||||
@@ -7,5 +7,10 @@
|
|||||||
"text2": "Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.",
|
"text2": "Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.",
|
||||||
"download": "Download Emergency Kit"
|
"download": "Download Emergency Kit"
|
||||||
},
|
},
|
||||||
"change-language": "Change Language"
|
"change-language": "Change Language",
|
||||||
|
"api-keys": {
|
||||||
|
"title": "API Keys",
|
||||||
|
"description": "Manage your personal API Keys to access the Infisical API.",
|
||||||
|
"add-new": "Add new"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user