mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 03:26:12 +00:00
Update package-lock.json
This commit is contained in:
+5
-5
@@ -31,12 +31,12 @@ MONGO_PASSWORD=example
|
|||||||
SITE_URL=http://localhost:8080
|
SITE_URL=http://localhost:8080
|
||||||
|
|
||||||
# Mail/SMTP
|
# Mail/SMTP
|
||||||
SMTP_HOST= # required
|
SMTP_HOST=
|
||||||
SMTP_USERNAME= # required
|
SMTP_USERNAME=
|
||||||
SMTP_PASSWORD= # required
|
SMTP_PASSWORD=
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_SECURE=false
|
SMTP_SECURE=false
|
||||||
SMTP_FROM_ADDRESS= # required
|
SMTP_FROM_ADDRESS=
|
||||||
SMTP_FROM_NAME=Infisical
|
SMTP_FROM_NAME=Infisical
|
||||||
|
|
||||||
# Integration
|
# Integration
|
||||||
@@ -66,4 +66,4 @@ STRIPE_WEBHOOK_SECRET=
|
|||||||
STRIPE_PRODUCT_STARTER=
|
STRIPE_PRODUCT_STARTER=
|
||||||
STRIPE_PRODUCT_TEAM=
|
STRIPE_PRODUCT_TEAM=
|
||||||
STRIPE_PRODUCT_PRO=
|
STRIPE_PRODUCT_PRO=
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
||||||
|
|||||||
Generated
+1336
-765
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.287.0",
|
"@aws-sdk/client-secrets-manager": "^3.294.0",
|
||||||
"@godaddy/terminus": "^4.11.2",
|
"@godaddy/terminus": "^4.11.2",
|
||||||
"@octokit/rest": "^19.0.5",
|
"@octokit/rest": "^19.0.5",
|
||||||
|
"@sentry/tracing": "^7.41.0",
|
||||||
"@sentry/node": "^7.40.0",
|
"@sentry/node": "^7.40.0",
|
||||||
"@sentry/tracing": "^7.39.0",
|
"@sentry/node": "^7.41.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/libsodium-wrappers": "^0.7.10",
|
"@types/libsodium-wrappers": "^0.7.10",
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.30.3",
|
||||||
"await-to-js": "^3.0.0",
|
"await-to-js": "^3.0.0",
|
||||||
"aws-sdk": "^2.1331.0",
|
"aws-sdk": "^2.1338.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
"axios-retry": "^3.4.0",
|
"axios-retry": "^3.4.0",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mongoose": "^6.10.3",
|
"mongoose": "^6.10.3",
|
||||||
"nodemailer": "^6.8.0",
|
"nodemailer": "^6.8.0",
|
||||||
"posthog-node": "^2.5.4",
|
"posthog-node": "^2.6.0",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"request-ip": "^3.3.0",
|
"request-ip": "^3.3.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
|||||||
@@ -75,8 +75,11 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
const secret = crypto.randomBytes(16).toString('hex');
|
const secret = crypto.randomBytes(16).toString('hex');
|
||||||
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
||||||
|
|
||||||
const expiresAt = new Date();
|
let expiresAt;
|
||||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
if (expiresIn) {
|
||||||
|
expiresAt = new Date()
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
let user, serviceAccount;
|
let user, serviceAccount;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ENV, POSTHOG_API_KEY, POSTHOG_HOST } from '../utilities/config';
|
|||||||
|
|
||||||
export const initPostHog = () => {
|
export const initPostHog = () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
console.log("Init Infisical")
|
console.log("Hi there 👋")
|
||||||
try {
|
try {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
import Button from '../basic/buttons/Button';
|
import Button from '../basic/buttons/Button';
|
||||||
import { DeleteEnvVar } from '../basic/dialog/DeleteEnvVar';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
@@ -13,7 +12,6 @@ type Props = {
|
|||||||
|
|
||||||
export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => {
|
export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${
|
<div className={`${
|
||||||
@@ -25,25 +23,17 @@ export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => {
|
|||||||
onKeyDown={() => null}
|
onKeyDown={() => null}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => setOpen(true)}
|
onClick={onSubmit}
|
||||||
className="invisible group-hover:visible"
|
className="invisible group-hover:visible"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon className="text-bunker-300 hover:text-red pl-2 pr-6 text-lg mt-0.5" icon={faXmark} />
|
<FontAwesomeIcon className="text-bunker-300 hover:text-red pl-2 pr-6 text-lg mt-0.5" icon={faXmark} />
|
||||||
</div>
|
</div>
|
||||||
: <Button
|
: <Button
|
||||||
text={String(t("Delete"))}
|
text={String(t("Delete"))}
|
||||||
// onButtonPressed={onSubmit}
|
|
||||||
color="red"
|
color="red"
|
||||||
size="md"
|
size="md"
|
||||||
onButtonPressed={() => setOpen(true)}
|
onButtonPressed={onSubmit}
|
||||||
/>}
|
/>}
|
||||||
<DeleteEnvVar
|
|
||||||
isOpen={open}
|
|
||||||
onClose={() => {
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,89 @@
|
|||||||
|
import { useRouter } from 'next/router';
|
||||||
import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
|
import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
import { useOrganization, useWorkspace } from '@app/context';
|
import { useOrganization, useWorkspace } from '@app/context';
|
||||||
|
|
||||||
|
import { Select, SelectItem, Tooltip } from '../v2';
|
||||||
|
|
||||||
// TODO: make links clickable and clean up
|
// TODO: make links clickable and clean up
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the component at the top of almost every page.
|
* This is the component at the top of almost every page.
|
||||||
* It shows how to navigate to a certain page.
|
* It shows how to navigate to a certain page.
|
||||||
* It future these links should also be clickable and hoverable
|
* It future these links should also be clickable and hoverable
|
||||||
* @param obj
|
* @param {object} obj
|
||||||
* @param obj.pageName - Name of the page
|
* @param {string} obj.pageName - Name of the page
|
||||||
* @param obj.isProjectRelated - whether or not this page is related to project (determine if it's 2 or 3 navigation steps)
|
* @param {boolean} obj.isProjectRelated - whether or not this page is related to project (determine if it's 2 or 3 navigation steps)
|
||||||
* @param obj.isOrganizationRelated - whether or not this page is related to organization (determine if it's 2 or 3 navigation steps)
|
* @param {boolean} obj.isOrganizationRelated - whether or not this page is related to organization (determine if it's 2 or 3 navigation steps)
|
||||||
|
* @param {string} obj.currentEnv - current environment inside a project
|
||||||
|
* @param {string} obj.userAvailableEnvs - environments that are available to a user in this project (used for the dropdown)
|
||||||
|
* @param {string} obj.onEnvChange - the action that happens when an env is changed
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function NavHeader({
|
export default function NavHeader({
|
||||||
pageName,
|
pageName,
|
||||||
isProjectRelated,
|
isProjectRelated,
|
||||||
isOrganizationRelated
|
isOrganizationRelated,
|
||||||
|
currentEnv,
|
||||||
|
userAvailableEnvs,
|
||||||
|
onEnvChange
|
||||||
}: {
|
}: {
|
||||||
pageName: string;
|
pageName: string;
|
||||||
isProjectRelated?: boolean;
|
isProjectRelated?: boolean;
|
||||||
isOrganizationRelated?: boolean;
|
isOrganizationRelated?: boolean;
|
||||||
|
currentEnv?: string;
|
||||||
|
userAvailableEnvs?: any[];
|
||||||
|
onEnvChange?: (slug: string) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ml-6 flex flex-row items-center pt-8">
|
<div className="ml-6 flex flex-row items-center pt-8">
|
||||||
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
|
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
|
||||||
{currentOrg?.name?.charAt(0)}
|
{currentOrg?.name?.charAt(0)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-semibold text-primary">{currentOrg?.name}</div>
|
<div className="text-sm font-semibold text-bunker-300">{currentOrg?.name}</div>
|
||||||
{isProjectRelated && (
|
{isProjectRelated && (
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-xs text-gray-400" />
|
||||||
<div className="text-sm font-semibold text-primary">{currentWorkspace?.name}</div>
|
<div className="text-sm font-semibold text-bunker-300">{currentWorkspace?.name}</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isOrganizationRelated && (
|
{isOrganizationRelated && (
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-xs text-gray-400" />
|
||||||
<div className="text-sm font-semibold text-primary">Organization Settings</div>
|
<div className="text-sm font-semibold text-bunker-300">Organization Settings</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
||||||
<div className="text-sm text-gray-400">{pageName}</div>
|
{pageName === 'Secrets'
|
||||||
|
? <a className="text-sm font-semibold text-primary/80 hover:text-primary" href={`${router.asPath.split("?")[0]}`}>{pageName}</a>
|
||||||
|
: <div className="text-sm text-gray-400">{pageName}</div>}
|
||||||
|
{currentEnv &&
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
|
||||||
|
<div className='pl-3 rounded-md hover:bg-bunker-100/10'>
|
||||||
|
<Tooltip content="Select environment">
|
||||||
|
<Select
|
||||||
|
value={userAvailableEnvs?.filter(uae => uae.name === currentEnv)[0]?.slug}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (value && onEnvChange) onEnvChange(value);
|
||||||
|
}}
|
||||||
|
className="text-sm pl-0 font-medium text-primary/80 hover:text-primary bg-transparent"
|
||||||
|
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
|
||||||
|
>
|
||||||
|
{userAvailableEnvs?.map(({ name, slug }) => (
|
||||||
|
<SelectItem value={slug} key={slug}>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const buttonVariants = cva(
|
|||||||
outline: ['bg-transparent', 'border-2', 'border-solid'],
|
outline: ['bg-transparent', 'border-2', 'border-solid'],
|
||||||
plain: '',
|
plain: '',
|
||||||
selected: '',
|
selected: '',
|
||||||
|
outline_bg: '',
|
||||||
// a constant color not in use on hover or click goes colorSchema color
|
// a constant color not in use on hover or click goes colorSchema color
|
||||||
star: 'text-bunker-200 bg-mineshaft-500'
|
star: 'text-bunker-200 bg-mineshaft-500'
|
||||||
},
|
},
|
||||||
@@ -67,6 +68,11 @@ const buttonVariants = cva(
|
|||||||
variant: 'selected',
|
variant: 'selected',
|
||||||
className: 'bg-primary/10 border border-primary/50 text-bunker-200'
|
className: 'bg-primary/10 border border-primary/50 text-bunker-200'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
colorSchema: 'primary',
|
||||||
|
variant: 'outline_bg',
|
||||||
|
className: 'bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60 text-bunker-200'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
colorSchema: 'secondary',
|
colorSchema: 'secondary',
|
||||||
variant: 'star',
|
variant: 'star',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { forwardRef, ReactNode } from 'react';
|
import { forwardRef, ReactNode } from 'react';
|
||||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||||
import { faCheck, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
import { faCaretDown, faCheck, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
@@ -49,7 +49,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
|||||||
</SelectPrimitive.Value>
|
</SelectPrimitive.Value>
|
||||||
{!isDisabled && (
|
{!isDisabled && (
|
||||||
<SelectPrimitive.Icon className="ml-3">
|
<SelectPrimitive.Icon className="ml-3">
|
||||||
<FontAwesomeIcon icon={faChevronDown} size="sm" />
|
<FontAwesomeIcon icon={faCaretDown} size="sm" />
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
)}
|
)}
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
@@ -76,7 +76,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
|||||||
)}
|
)}
|
||||||
</SelectPrimitive.Viewport>
|
</SelectPrimitive.Viewport>
|
||||||
<SelectPrimitive.ScrollDownButton>
|
<SelectPrimitive.ScrollDownButton>
|
||||||
<FontAwesomeIcon icon={faChevronDown} size="sm" />
|
<FontAwesomeIcon icon={faCaretDown} size="xs" />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownButton>
|
||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Content>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const SecretVersionList = ({ secretId }: { secretId: string }) => {
|
|||||||
}, [secretId]);
|
}, [secretId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full min-w-40 h-[12.4rem] px-4 mt-4 text-sm text-bunker-300 overflow-x-none">
|
<div className="w-full min-w-40 h-[12.4rem] px-4 mt-4 text-sm text-bunker-300 overflow-x-none dark">
|
||||||
<p className="">{t('dashboard:sidebar.version-history')}</p>
|
<p className="">{t('dashboard:sidebar.version-history')}</p>
|
||||||
<div className="pl-1 py-0.5 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none h-full">
|
<div className="pl-1 py-0.5 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none h-full">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -102,7 +102,7 @@ const SecretVersionList = ({ secretId }: { secretId: string }) => {
|
|||||||
<div className="w-0 h-full border-l border-bunker-300 mt-1" />
|
<div className="w-0 h-full border-l border-bunker-300 mt-1" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full max-w-[calc(100%-2.3rem)]">
|
<div className="flex flex-col w-full max-w-[calc(100%-2.3rem)]">
|
||||||
<div className="pr-2 pt-1 text-bunker-300/90">
|
<div className="pr-2 text-bunker-300/90">
|
||||||
{new Date(version.createdAt).toLocaleDateString('en-US', {
|
{new Date(version.createdAt).toLocaleDateString('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
|
|||||||
@@ -19,18 +19,42 @@ import {
|
|||||||
|
|
||||||
export const secretKeys = {
|
export const secretKeys = {
|
||||||
// this is also used in secretSnapshot part
|
// this is also used in secretSnapshot part
|
||||||
getProjectSecret: (workspaceId: string, env: string) => [{ workspaceId, env }, 'secrets'],
|
getProjectSecret: (workspaceId: string, env: string | string[]) => [{ workspaceId, env }, 'secrets'],
|
||||||
getSecretVersion: (secretId: string) => [{ secretId }, 'secret-versions']
|
getSecretVersion: (secretId: string) => [{ secretId }, 'secret-versions']
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string) => {
|
const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | string[]) => {
|
||||||
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
|
if (typeof env === 'string') {
|
||||||
params: {
|
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
|
||||||
environment: env,
|
params: {
|
||||||
workspaceId
|
environment: env,
|
||||||
|
workspaceId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data.secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof env === 'object') {
|
||||||
|
let allEnvData: any = [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const envPoint of env) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
|
||||||
|
params: {
|
||||||
|
environment: envPoint,
|
||||||
|
workspaceId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
allEnvData = allEnvData.concat(data.secrets);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return data.secrets;
|
return allEnvData;
|
||||||
|
// eslint-disable-next-line no-else-return
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetProjectSecrets = ({
|
export const useGetProjectSecrets = ({
|
||||||
@@ -59,7 +83,7 @@ export const useGetProjectSecrets = ({
|
|||||||
// this used for add-only mode in dashboard
|
// this used for add-only mode in dashboard
|
||||||
// type won't be there thus only one key is shown
|
// type won't be there thus only one key is shown
|
||||||
const duplicateSecretKey: Record<string, boolean> = {};
|
const duplicateSecretKey: Record<string, boolean> = {};
|
||||||
data.forEach((encSecret) => {
|
data.forEach((encSecret: EncryptedSecret) => {
|
||||||
const secretKey = decryptSymmetric({
|
const secretKey = decryptSymmetric({
|
||||||
ciphertext: encSecret.secretKeyCiphertext,
|
ciphertext: encSecret.secretKeyCiphertext,
|
||||||
iv: encSecret.secretKeyIV,
|
iv: encSecret.secretKeyIV,
|
||||||
@@ -93,12 +117,12 @@ export const useGetProjectSecrets = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (encSecret.type === 'personal') {
|
if (encSecret.type === 'personal') {
|
||||||
personalSecrets[decryptedSecret.key] = { id: encSecret._id, value: secretValue };
|
personalSecrets[`${decryptedSecret.key}-${decryptedSecret.env}`] = { id: encSecret._id, value: secretValue };
|
||||||
} else {
|
} else {
|
||||||
if (!duplicateSecretKey?.[decryptedSecret.key]) {
|
if (!duplicateSecretKey?.[`${decryptedSecret.key}-${decryptedSecret.env}`]) {
|
||||||
sharedSecrets.push(decryptedSecret);
|
sharedSecrets.push(decryptedSecret);
|
||||||
}
|
}
|
||||||
duplicateSecretKey[decryptedSecret.key] = true;
|
duplicateSecretKey[`${decryptedSecret.key}-${decryptedSecret.env}`] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sharedSecrets.forEach((val) => {
|
sharedSecrets.forEach((val) => {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export type BatchSecretDTO = {
|
|||||||
|
|
||||||
export type GetProjectSecretsDTO = {
|
export type GetProjectSecretsDTO = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
env: string;
|
env: string | string[];
|
||||||
decryptFileKey: UserWsKeyPair;
|
decryptFileKey: UserWsKeyPair;
|
||||||
isPaused?: boolean;
|
isPaused?: boolean;
|
||||||
onSuccess?: (data: DecryptedSecret[]) => void;
|
onSuccess?: (data: DecryptedSecret[]) => void;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import queryString from 'query-string';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
|
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
|
||||||
@@ -158,7 +159,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
.map((workspace: { _id: string }) => workspace._id)
|
.map((workspace: { _id: string }) => workspace._id)
|
||||||
.includes(intendedWorkspaceId)
|
.includes(intendedWorkspaceId)
|
||||||
) {
|
) {
|
||||||
router.push(`/dashboard/${userWorkspaces[0]._id}`);
|
const { env } = queryString.parse(router.asPath.split('?')[1]);
|
||||||
|
if (!env) {
|
||||||
|
router.push(`/dashboard/${userWorkspaces[0]._id}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setWorkspaceMapping(
|
setWorkspaceMapping(
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
@@ -271,11 +275,12 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
{name}
|
{name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<hr className="mt-1 mb-1 h-px border-0 bg-gray-700" />
|
{/* <hr className="mt-1 mb-1 h-px border-0 bg-gray-700" /> */}
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Button
|
<Button
|
||||||
className="w-full py-2 text-bunker-200 bg-mineshaft-500 hover:bg-primary/90 hover:text-black"
|
className="w-full py-2 text-bunker-200 bg-mineshaft-700"
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handlePopUpOpen('addNewWs')}
|
onClick={() => handlePopUpOpen('addNewWs')}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Tag } from 'public/data/frequentInterfaces';
|
import { Tag } from 'public/data/frequentInterfaces';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
|
||||||
import Button from '@app/components/basic/buttons/Button';
|
import Button from '@app/components/basic/buttons/Button';
|
||||||
import ListBox from '@app/components/basic/Listbox';
|
|
||||||
import BottonRightPopup from '@app/components/basic/popups/BottomRightPopup';
|
import BottonRightPopup from '@app/components/basic/popups/BottomRightPopup';
|
||||||
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
|
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
|
||||||
import ConfirmEnvOverwriteModal from '@app/components/dashboard/ConfirmEnvOverwriteModal';
|
import ConfirmEnvOverwriteModal from '@app/components/dashboard/ConfirmEnvOverwriteModal';
|
||||||
@@ -41,6 +41,8 @@ import getProjectSercetSnapshotsCount from '@app/ee/api/secrets/GetProjectSercet
|
|||||||
import performSecretRollback from '@app/ee/api/secrets/PerformSecretRollback';
|
import performSecretRollback from '@app/ee/api/secrets/PerformSecretRollback';
|
||||||
import PITRecoverySidebar from '@app/ee/components/PITRecoverySidebar';
|
import PITRecoverySidebar from '@app/ee/components/PITRecoverySidebar';
|
||||||
import { useLeaveConfirm } from '@app/hooks';
|
import { useLeaveConfirm } from '@app/hooks';
|
||||||
|
// import { DashboardPage } from '@app/views/DashboardPage';
|
||||||
|
import { DashboardEnvOverview } from '@app/views/DashboardPage/DashboardEnvOverview';
|
||||||
|
|
||||||
// import addSecrets from '../api/files/AddSecrets';
|
// import addSecrets from '../api/files/AddSecrets';
|
||||||
// import deleteSecrets from '../api/files/DeleteSecrets';
|
// import deleteSecrets from '../api/files/DeleteSecrets';
|
||||||
@@ -147,7 +149,6 @@ function findDuplicates(arr: any[]) {
|
|||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [data, setData] = useState<SecretDataProps[] | null>();
|
const [data, setData] = useState<SecretDataProps[] | null>();
|
||||||
const [initialData, setInitialData] = useState<SecretDataProps[] | null | undefined>([]);
|
const [initialData, setInitialData] = useState<SecretDataProps[] | null | undefined>([]);
|
||||||
const router = useRouter();
|
|
||||||
const [blurred, setBlurred] = useState(true);
|
const [blurred, setBlurred] = useState(true);
|
||||||
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
|
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
|
||||||
const [isNew, setIsNew] = useState(false);
|
const [isNew, setIsNew] = useState(false);
|
||||||
@@ -169,7 +170,10 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({ initialValue: false });
|
const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({ initialValue: false });
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
|
const router = useRouter();
|
||||||
|
const envInURL = queryString.parse(router.asPath.split('?')[1])?.env;
|
||||||
|
|
||||||
const workspaceId = router.query.id as string;
|
const workspaceId = router.query.id as string;
|
||||||
const [workspaceEnvs, setWorkspaceEnvs] = useState<WorkspaceEnv[]>([]);
|
const [workspaceEnvs, setWorkspaceEnvs] = useState<WorkspaceEnv[]>([]);
|
||||||
@@ -783,13 +787,13 @@ export default function Dashboard() {
|
|||||||
deleteRow({ ids, secretName });
|
deleteRow({ ids, secretName });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnEnvironmentChange = (envName: string) => {
|
const handleOnEnvironmentChange = (envSlug: string) => {
|
||||||
if (hasUnsavedChanges) {
|
if (hasUnsavedChanges) {
|
||||||
if (!window.confirm(leaveConfirmDefaultMessage)) return;
|
if (!window.confirm(leaveConfirmDefaultMessage)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedWorkspaceEnv = workspaceEnvs.find(
|
const selectedWorkspaceEnv = workspaceEnvs.find(
|
||||||
({ name }: { name: string }) => envName === name
|
({ slug }: { slug: string }) => envSlug === slug
|
||||||
) || {
|
) || {
|
||||||
name: 'unknown',
|
name: 'unknown',
|
||||||
slug: 'unknown',
|
slug: 'unknown',
|
||||||
@@ -797,6 +801,8 @@ export default function Dashboard() {
|
|||||||
isReadDenied: false
|
isReadDenied: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(124, envSlug, selectedWorkspaceEnv)
|
||||||
|
|
||||||
if (selectedWorkspaceEnv) {
|
if (selectedWorkspaceEnv) {
|
||||||
if (snapshotData) setSelectedSnapshotEnv(selectedWorkspaceEnv);
|
if (snapshotData) setSelectedSnapshotEnv(selectedWorkspaceEnv);
|
||||||
else setSelectedEnv(selectedWorkspaceEnv);
|
else setSelectedEnv(selectedWorkspaceEnv);
|
||||||
@@ -809,7 +815,10 @@ export default function Dashboard() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
return data ? (
|
return <div>
|
||||||
|
{!envInURL
|
||||||
|
? <DashboardEnvOverview />
|
||||||
|
: (data ? (
|
||||||
<div className="bg-bunker-800 max-h-screen h-full relative flex flex-col justify-between text-white dark">
|
<div className="bg-bunker-800 max-h-screen h-full relative flex flex-col justify-between text-white dark">
|
||||||
<Head>
|
<Head>
|
||||||
<title>{t('common:head-title', { title: t('dashboard:title') })}</title>
|
<title>{t('common:head-title', { title: t('dashboard:title') })}</title>
|
||||||
@@ -835,7 +844,13 @@ export default function Dashboard() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="w-full max-h-96 pb-2 dark:[color-scheme:dark]">
|
<div className="w-full max-h-96 pb-2 dark:[color-scheme:dark]">
|
||||||
<NavHeader pageName={t('dashboard:title')} isProjectRelated />
|
<NavHeader
|
||||||
|
pageName={t('dashboard:title')}
|
||||||
|
currentEnv={workspaceEnvs?.filter(envir => envir.slug === envInURL)[0].name || ''}
|
||||||
|
isProjectRelated
|
||||||
|
userAvailableEnvs={workspaceEnvs}
|
||||||
|
onEnvChange={handleOnEnvironmentChange}
|
||||||
|
/>
|
||||||
{checkDocsPopUpVisible && (
|
{checkDocsPopUpVisible && (
|
||||||
<BottonRightPopup
|
<BottonRightPopup
|
||||||
buttonText={t('dashboard:check-docs.button')}
|
buttonText={t('dashboard:check-docs.button')}
|
||||||
@@ -851,7 +866,7 @@ export default function Dashboard() {
|
|||||||
{snapshotData && (
|
{snapshotData && (
|
||||||
<div className="flex justify-start max-w-sm mt-1 mr-2">
|
<div className="flex justify-start max-w-sm mt-1 mr-2">
|
||||||
<Button
|
<Button
|
||||||
text={String(t('Go back to current'))}
|
text={String(t('To Current'))}
|
||||||
onButtonPressed={() => setSnapshotData(undefined)}
|
onButtonPressed={() => setSnapshotData(undefined)}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
size="md"
|
size="md"
|
||||||
@@ -863,7 +878,7 @@ export default function Dashboard() {
|
|||||||
<div className="font-semibold mr-4 mt-1 flex flex-row items-center">
|
<div className="font-semibold mr-4 mt-1 flex flex-row items-center">
|
||||||
<p>{snapshotData ? 'Secret Snapshot' : t('dashboard:title')}</p>
|
<p>{snapshotData ? 'Secret Snapshot' : t('dashboard:title')}</p>
|
||||||
{snapshotData && (
|
{snapshotData && (
|
||||||
<span className="bg-primary-800 text-sm ml-4 mt-1 px-1.5 rounded-md">
|
<span className="bg-primary-800 text-xs ml-4 mt-1 px-1.5 rounded-md w-min">
|
||||||
{new Date(snapshotData.createdAt).toLocaleString()}
|
{new Date(snapshotData.createdAt).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -873,13 +888,6 @@ export default function Dashboard() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!snapshotData && data?.length === 0 && selectedEnv && (
|
|
||||||
<ListBox
|
|
||||||
isSelected={selectedEnv.name}
|
|
||||||
data={workspaceEnvs.map(({ name }) => name)}
|
|
||||||
onChange={handleOnEnvironmentChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="flex justify-start max-w-sm mt-1 mr-2">
|
<div className="flex justify-start max-w-sm mt-1 mr-2">
|
||||||
@@ -960,20 +968,7 @@ export default function Dashboard() {
|
|||||||
<div className="w-full flex flex-row items-start">
|
<div className="w-full flex flex-row items-start">
|
||||||
{(snapshotData || data?.length !== 0) && selectedEnv && (
|
{(snapshotData || data?.length !== 0) && selectedEnv && (
|
||||||
<>
|
<>
|
||||||
{!snapshotData ? (
|
<div className="h-10 w-full bg-mineshaft-700 hover:bg-white/10 rounded-md flex flex-row items-center">
|
||||||
<ListBox
|
|
||||||
isSelected={selectedEnv.name}
|
|
||||||
data={workspaceEnvs.map(({ name }) => name)}
|
|
||||||
onChange={handleOnEnvironmentChange}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ListBox
|
|
||||||
isSelected={selectedSnapshotEnv?.name || ''}
|
|
||||||
data={workspaceEnvs.map(({ name }) => name)}
|
|
||||||
onChange={handleOnEnvironmentChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="h-10 w-full bg-mineshaft-700 hover:bg-white/10 ml-2 rounded-md flex flex-row items-center">
|
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
className="bg-transparent rounded-l-md py-[0.7rem] pl-4 pr-2 text-bunker-300 text-sm"
|
className="bg-transparent rounded-l-md py-[0.7rem] pl-4 pr-2 text-bunker-300 text-sm"
|
||||||
icon={faMagnifyingGlass}
|
icon={faMagnifyingGlass}
|
||||||
@@ -1023,7 +1018,7 @@ export default function Dashboard() {
|
|||||||
<div className="flex items-center justify-center h-full my-48">
|
<div className="flex items-center justify-center h-full my-48">
|
||||||
<Image
|
<Image
|
||||||
src="/images/loading/loading.gif"
|
src="/images/loading/loading.gif"
|
||||||
height={60}
|
height={600}
|
||||||
width={100}
|
width={100}
|
||||||
alt="infisical loading indicator"
|
alt="infisical loading indicator"
|
||||||
/>
|
/>
|
||||||
@@ -1223,10 +1218,10 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
<div className="relative z-10 w-10/12 mr-auto h-screen ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||||
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
</div>
|
</div>
|
||||||
);
|
))}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
Dashboard.requireAuth = true;
|
Dashboard.requireAuth = true;
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { FormProvider, useForm, useWatch } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
|
||||||
|
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
|
||||||
|
import NavHeader from '@app/components/navigation/NavHeader';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
TableContainer,
|
||||||
|
Tooltip
|
||||||
|
} from '@app/components/v2';
|
||||||
|
import { useWorkspace } from '@app/context';
|
||||||
|
import { usePopUp } from '@app/hooks';
|
||||||
|
import {
|
||||||
|
useCreateWsTag,
|
||||||
|
useGetProjectSecrets,
|
||||||
|
useGetUserWsEnvironments,
|
||||||
|
useGetUserWsKey,
|
||||||
|
} from '@app/hooks/api';
|
||||||
|
import { WorkspaceEnv } from '@app/hooks/api/types';
|
||||||
|
|
||||||
|
import { CreateTagModal } from './components/CreateTagModal';
|
||||||
|
import { EnvComparisonRow } from './components/EnvComparisonRow';
|
||||||
|
import {
|
||||||
|
FormData,
|
||||||
|
schema
|
||||||
|
} from './DashboardPage.utils';
|
||||||
|
|
||||||
|
|
||||||
|
export const DashboardEnvOverview = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
|
const { popUp
|
||||||
|
// , handlePopUpOpen
|
||||||
|
, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
|
'secretDetails',
|
||||||
|
'addTag',
|
||||||
|
'secretSnapshots',
|
||||||
|
'uploadedSecOpts',
|
||||||
|
'compareSecrets'
|
||||||
|
] as const);
|
||||||
|
const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv | null>(null);
|
||||||
|
|
||||||
|
const { currentWorkspace, isLoading } = useWorkspace();
|
||||||
|
const workspaceId = currentWorkspace?._id as string;
|
||||||
|
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && !workspaceId && router.isReady) {
|
||||||
|
router.push('/noprojects');
|
||||||
|
}
|
||||||
|
}, [isLoading, workspaceId, router.isReady]);
|
||||||
|
|
||||||
|
const { data: wsEnv, isLoading: isEnvListLoading } = useGetUserWsEnvironments({
|
||||||
|
workspaceId,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
// get an env with one of the access available
|
||||||
|
const env = data.find(({ isReadDenied }) => !isReadDenied);
|
||||||
|
if (env) {
|
||||||
|
setSelectedEnv(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const userAvailableEnvs = wsEnv?.filter(
|
||||||
|
({ isReadDenied }) => !isReadDenied
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({
|
||||||
|
workspaceId,
|
||||||
|
env: userAvailableEnvs?.map(env => env.slug) ?? [],
|
||||||
|
decryptFileKey: latestFileKey!,
|
||||||
|
isPaused: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// mutation calls
|
||||||
|
const { mutateAsync: createWsTag } = useCreateWsTag();
|
||||||
|
|
||||||
|
const method = useForm<FormData>({
|
||||||
|
// why any: well yup inferred ts expects other keys to defined as undefined
|
||||||
|
defaultValues: secrets as any,
|
||||||
|
values: secrets as any,
|
||||||
|
mode: 'onBlur',
|
||||||
|
resolver: yupResolver(schema)
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
// handleSubmit,
|
||||||
|
// getValues,
|
||||||
|
// setValue,
|
||||||
|
// formState: { isSubmitting, dirtyFields },
|
||||||
|
// reset
|
||||||
|
} = method;
|
||||||
|
const formSecrets = useWatch({ control, name: 'secrets' });
|
||||||
|
|
||||||
|
const isReadOnly = selectedEnv?.isWriteDenied;
|
||||||
|
|
||||||
|
const onCreateWsTag = async (tagName: string) => {
|
||||||
|
try {
|
||||||
|
await createWsTag({
|
||||||
|
workspaceID: workspaceId,
|
||||||
|
tagName,
|
||||||
|
tagSlug: tagName.replace(' ', '_')
|
||||||
|
});
|
||||||
|
handlePopUpClose('addTag');
|
||||||
|
createNotification({
|
||||||
|
text: 'Successfully created a tag',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
createNotification({
|
||||||
|
text: 'Failed to create a tag',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isSecretsLoading || isEnvListLoading) {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto flex h-full w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
|
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when secrets is not loading and secrets list is empty
|
||||||
|
const isDashboardSecretEmpty = !isSecretsLoading && !formSecrets?.length;
|
||||||
|
|
||||||
|
const numSecretsMissingPerEnv = userAvailableEnvs?.map(envir => ({[envir.slug]: [... new Set(secrets?.secrets.map((secret: any) => secret.key))].length - [... new Set(secrets?.secrets.filter(s => s.env === envir.slug).map((secret: any) => secret.key))].length})).reduce((acc, cur) => ({ ...acc, ...cur }), {})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto max-w-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
|
<FormProvider {...method}>
|
||||||
|
<form autoComplete="off">
|
||||||
|
{/* breadcrumb row */}
|
||||||
|
<div className="relative right-5">
|
||||||
|
<NavHeader pageName={t('dashboard:title')} isProjectRelated />
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 ml-1">
|
||||||
|
<p className="text-3xl font-semibold text-bunker-100">Secrets Overview</p>
|
||||||
|
<p className="text-md text-bunker-300">Inject your secrets using
|
||||||
|
<a
|
||||||
|
className="text-primary/80 hover:text-primary mx-1"
|
||||||
|
href="https://infisical.com/docs/cli/overview"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Infisical CLI
|
||||||
|
</a>
|
||||||
|
or
|
||||||
|
<a
|
||||||
|
className="text-primary/80 hover:text-primary mx-1"
|
||||||
|
href="https://infisical.com/docs/sdks/overview"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Infisical SDKs
|
||||||
|
</a> </p>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-y-auto">
|
||||||
|
<div className="sticky top-0 absolute flex flex-row h-10 bg-mineshaft-800 border border-mineshaft-600 rounded-md mt-8 min-w-[60.3rem]">
|
||||||
|
<div className="sticky top-0 w-10 px-4 flex items-center justify-center border-none">
|
||||||
|
<div className='text-center w-10 text-xs text-transparent'>{0}</div>
|
||||||
|
</div>
|
||||||
|
<div className="sticky top-0 border-none">
|
||||||
|
<div className="min-w-[200px] lg:min-w-[220px] xl:min-w-[250px] relative flex items-center justify-start h-full w-full">
|
||||||
|
<div className="text-sm font-medium ">Secret</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{numSecretsMissingPerEnv && userAvailableEnvs?.map(env => {
|
||||||
|
return <div key={`header-${env.slug}`} className="flex flex-row w-full bg-mineshaft-800 rounded-md items-center border-none min-w-[11rem]">
|
||||||
|
<div className="text-sm font-medium w-full text-center text-bunker-200/[.99] flex flex-row justify-center">
|
||||||
|
{env.name}
|
||||||
|
{numSecretsMissingPerEnv[env.slug] > 0 && <div className="bg-red rounded-sm h-[1.1rem] w-[1.1rem] mt-0.5 text-bunker-100 ml-2.5 text-xs border border-red-400 flex items-center justify-center cursor-default">
|
||||||
|
<Tooltip content={`${numSecretsMissingPerEnv[env.slug]} secrets missing compared to other environments`}><span className="text-bunker-100">{numSecretsMissingPerEnv[env.slug]}</span></Tooltip>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className={`${isDashboardSecretEmpty ? "" : ""} flex flex-row items-start justify-center mt-3 h-full max-h-[calc(100vh-370px)] min-w-[60.3rem] flex-grow w-full overflow-x-hidden no-scrollbar no-scrollbar::-webkit-scrollbar`}>
|
||||||
|
{!isDashboardSecretEmpty && (
|
||||||
|
<TableContainer className='border-none'>
|
||||||
|
<table className="relative secret-table w-full relative bg-bunker-800">
|
||||||
|
<tbody className="overflow-y-auto max-h-screen">
|
||||||
|
{[... new Set(secrets?.secrets.map((secret: any) => secret.key))].map((key, index) => (
|
||||||
|
<EnvComparisonRow
|
||||||
|
key={`row-${key}`}
|
||||||
|
secrets={secrets?.secrets.filter(secret => secret.key === key)}
|
||||||
|
isReadOnly={isReadOnly}
|
||||||
|
index={index}
|
||||||
|
isSecretValueHidden
|
||||||
|
userAvailableEnvs={userAvailableEnvs}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
{isDashboardSecretEmpty &&
|
||||||
|
<div className='flex flex-row h-40 rounded-md mt-1 w-full'>
|
||||||
|
<div className="sticky top-0 w-10 px-4 flex items-center justify-center border-none">
|
||||||
|
<div className='text-center w-10 text-xs text-transparent'>{0}</div>
|
||||||
|
</div>
|
||||||
|
<div className="sticky top-0 border-none">
|
||||||
|
<div className="min-w-[200px] lg:min-w-[220px] xl:min-w-[250px] relative flex items-center justify-start h-full w-full">
|
||||||
|
<div className="text-sm font-medium text-transparent">Secret</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col w-full bg-mineshaft-800 text-bunker-300 rounded-md items-center justify-center border-none mx-2 min-w-[11rem]">
|
||||||
|
<span className="mb-1">No secrets are available in this project yet.</span>
|
||||||
|
<span>You can go into any environment to add secrets there.</span>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
{/* In future, we should add an option to add environments here
|
||||||
|
<div className="ml-10 h-full flex items-start justify-center">
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus}/>}
|
||||||
|
onClick={() => prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false })}
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="primary"
|
||||||
|
isFullWidth
|
||||||
|
className="h-10"
|
||||||
|
>
|
||||||
|
Add Environment
|
||||||
|
</Button>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
<div className="group min-w-full flex flex-row items-center mt-4">
|
||||||
|
<div className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-transparent'>0</div></div>
|
||||||
|
<div className="flex flex-row justify-between items-center min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]">
|
||||||
|
<span className="text-transparent">0</span>
|
||||||
|
<button type="button" className='mr-2 text-transparent'>1</button>
|
||||||
|
</div>
|
||||||
|
{userAvailableEnvs?.map(env => {
|
||||||
|
return <div key={`button-${env.slug}`} className="flex flex-row w-full justify-center h-10 items-center border-none mb-1 mx-2 min-w-[10rem]">
|
||||||
|
<Button
|
||||||
|
onClick={() => router.push(`${router.asPath }?env=${env.slug}`)}
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="primary"
|
||||||
|
isFullWidth
|
||||||
|
className="h-10"
|
||||||
|
>
|
||||||
|
Explore {env.name}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.addTag?.isOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
handlePopUpToggle('addTag', open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title="Create tag"
|
||||||
|
subTitle="Specify your tag name, and the slug will be created automatically."
|
||||||
|
>
|
||||||
|
<CreateTagModal onCreateTag={onCreateWsTag} />
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -28,8 +28,6 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
Select,
|
|
||||||
SelectItem,
|
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Tag,
|
Tag,
|
||||||
Tooltip
|
Tooltip
|
||||||
@@ -86,7 +84,7 @@ const USER_ACTION_PUSH = 'first_time_secrets_pushed';
|
|||||||
* Instead when user delete we raise a flag so if user decides to go back to toggle personal before saving
|
* Instead when user delete we raise a flag so if user decides to go back to toggle personal before saving
|
||||||
* They will get it back
|
* They will get it back
|
||||||
*/
|
*/
|
||||||
export const DashboardPage = () => {
|
export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
@@ -126,8 +124,8 @@ export const DashboardPage = () => {
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
// get an env with one of the access available
|
// get an env with one of the access available
|
||||||
const env = data.find(({ isReadDenied, isWriteDenied }) => !isWriteDenied || !isReadDenied);
|
const env = data.find(({ isReadDenied, isWriteDenied }) => !isWriteDenied || !isReadDenied);
|
||||||
if (env) {
|
if (env && data?.map(wsenv => wsenv.slug).includes(envFromTop)) {
|
||||||
setSelectedEnv(env);
|
setSelectedEnv(data?.filter(dp => dp.slug === envFromTop)[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -357,6 +355,7 @@ export const DashboardPage = () => {
|
|||||||
}
|
}
|
||||||
const env = wsEnv?.find((el) => el.slug === slug);
|
const env = wsEnv?.find((el) => el.slug === slug);
|
||||||
if (env) setSelectedEnv(env);
|
if (env) setSelectedEnv(env);
|
||||||
|
router.push(`${router.asPath.split("?")[0]}?env=${slug}`)
|
||||||
};
|
};
|
||||||
|
|
||||||
// record all deleted ids
|
// record all deleted ids
|
||||||
@@ -417,7 +416,13 @@ export const DashboardPage = () => {
|
|||||||
<form autoComplete="off">
|
<form autoComplete="off">
|
||||||
{/* breadcrumb row */}
|
{/* breadcrumb row */}
|
||||||
<div className="relative right-5">
|
<div className="relative right-5">
|
||||||
<NavHeader pageName={t('dashboard:title')} isProjectRelated />
|
<NavHeader
|
||||||
|
pageName={t('dashboard:title')}
|
||||||
|
currentEnv={userAvailableEnvs?.filter(envir => envir.slug === envFromTop)[0].name || ''}
|
||||||
|
isProjectRelated
|
||||||
|
userAvailableEnvs={userAvailableEnvs}
|
||||||
|
onEnvChange={onEnvChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Secrets, commit and save button section */}
|
{/* Secrets, commit and save button section */}
|
||||||
<div className="mt-6 flex items-center justify-between">
|
<div className="mt-6 flex items-center justify-between">
|
||||||
@@ -468,23 +473,6 @@ export const DashboardPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* Environment, search and other action row */}
|
{/* Environment, search and other action row */}
|
||||||
<div className="mt-4 flex items-center space-x-2">
|
<div className="mt-4 flex items-center space-x-2">
|
||||||
<div>
|
|
||||||
<Tooltip content="Select environment">
|
|
||||||
<Select
|
|
||||||
value={selectedEnv?.slug}
|
|
||||||
onValueChange={onEnvChange}
|
|
||||||
position="popper"
|
|
||||||
className="min-w-[180px] bg-mineshaft-600 h-10 font-medium"
|
|
||||||
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
|
|
||||||
>
|
|
||||||
{userAvailableEnvs?.map(({ name, slug }) => (
|
|
||||||
<SelectItem value={slug} key={slug}>
|
|
||||||
{name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<Input
|
<Input
|
||||||
className="bg-mineshaft-600 h-[2.3rem] placeholder-mineshaft-50"
|
className="bg-mineshaft-600 h-[2.3rem] placeholder-mineshaft-50"
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/* eslint-disable react/jsx-no-useless-fragment */
|
||||||
|
import { SyntheticEvent, useRef, useState } from 'react';
|
||||||
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
|
import guidGenerator from '@app/components/utilities/randomId';
|
||||||
|
|
||||||
|
import { FormData } from '../../DashboardPage.utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
index: number;
|
||||||
|
secrets: any[] | undefined;
|
||||||
|
// permission and external state's that decided to hide or show
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
isSecretValueHidden: boolean;
|
||||||
|
userAvailableEnvs?: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const REGEX = /([$]{.*?})/g;
|
||||||
|
|
||||||
|
const DashboardInput = ({ isOverridden, isSecretValueHidden, isReadOnly, secret, index }: { isOverridden: boolean, isSecretValueHidden: boolean, isReadOnly?: boolean, secret: any, index: number } ): JSX.Element => {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
|
||||||
|
if (ref.current === null) return;
|
||||||
|
|
||||||
|
ref.current.scrollTop = e.currentTarget.scrollTop;
|
||||||
|
ref.current.scrollLeft = e.currentTarget.scrollLeft;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <td key={`row-${secret?.key || ''}--`} className={`flex flex-row w-full min-w-[11rem] justify-center h-10 items-center ${!(secret?.value || secret?.value === '') ? "bg-red-400/10" : "bg-mineshaft-900/30"}`}>
|
||||||
|
<div className="group relative whitespace-pre flex flex-col justify-center w-full">
|
||||||
|
<input
|
||||||
|
// {...register(`secrets.${index}.valueOverride`)}
|
||||||
|
defaultValue={(isOverridden ? secret.valueOverride : secret?.value || '')}
|
||||||
|
onScroll={syncScroll}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
className={`${
|
||||||
|
isSecretValueHidden
|
||||||
|
? 'text-transparent focus:text-transparent active:text-transparent'
|
||||||
|
: ''
|
||||||
|
} z-10 peer font-mono ph-no-capture bg-transparent caret-transparent text-transparent text-sm px-2 py-2 w-full outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||||
|
spellCheck="false"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`${
|
||||||
|
isSecretValueHidden && !isOverridden && secret?.value
|
||||||
|
? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400 duration-200'
|
||||||
|
: ''
|
||||||
|
} ${!secret?.value && "text-bunker-400 justify-center"}
|
||||||
|
absolute flex flex-row whitespace-pre font-mono z-0 ${isSecretValueHidden && secret?.value ? 'invisible' : 'visible'} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||||
|
>
|
||||||
|
{(secret?.value || secret?.value === '') && (isOverridden ? secret.valueOverride : secret?.value)?.split('').length === 0 && <span className='text-bunker-400/80 font-sans w-full'>EMPTY</span>}
|
||||||
|
{(secret?.value || secret?.value === '') && (isOverridden ? secret.valueOverride : secret?.value)?.split(REGEX).map((word: string) => {
|
||||||
|
if (word.match(REGEX) !== null) {
|
||||||
|
return (
|
||||||
|
<span className="ph-no-capture text-yellow" key={word}>
|
||||||
|
{word.slice(0, 2)}
|
||||||
|
<span className="ph-no-capture text-yellow-200/80">
|
||||||
|
{word.slice(2, word.length - 1)}
|
||||||
|
</span>
|
||||||
|
{word.slice(word.length - 1, word.length) === '}' ? (
|
||||||
|
<span className="ph-no-capture text-yellow">
|
||||||
|
{word.slice(word.length - 1, word.length)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="ph-no-capture text-yellow-400">
|
||||||
|
{word.slice(word.length - 1, word.length)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span key={`${word}_${index + 1}`} className="ph-no-capture">
|
||||||
|
{word}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{!(secret?.value || secret?.value === '') && <span className='text-red-500/80 font-sans text-xs italic'>missing</span>}
|
||||||
|
</div>
|
||||||
|
{(isSecretValueHidden && secret?.value) && (
|
||||||
|
<div className='absolute flex flex-row justify-between items-center z-0 peer pr-2 peer-active:hidden peer-focus:hidden group-hover:bg-white/[0.00] duration-100 h-10 w-full text-bunker-400 text-clip'>
|
||||||
|
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||||
|
{(isOverridden ? secret.valueOverride : secret?.value || '')?.split('').map(() => (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
key={guidGenerator()}
|
||||||
|
className="text-xxs mr-0.5"
|
||||||
|
icon={faCircle}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{(isOverridden ? secret.valueOverride : secret?.value || '')?.split('').length === 0 && <span className='text-bunker-400/80 text-sm'>EMPTY</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnvComparisonRow = ({
|
||||||
|
index,
|
||||||
|
secrets,
|
||||||
|
isSecretValueHidden,
|
||||||
|
isReadOnly,
|
||||||
|
userAvailableEnvs
|
||||||
|
}: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
// register, setValue,
|
||||||
|
control } = useFormContext<FormData>();
|
||||||
|
|
||||||
|
// to get details on a secret
|
||||||
|
const secret = useWatch({ name: `secrets.${index}`, control });
|
||||||
|
|
||||||
|
const [areValuesHiddenThisRow, setAreValuesHiddenThisRow] = useState(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className="group min-w-full flex flex-row items-center hover:bg-bunker-700">
|
||||||
|
<td className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-bunker-400'>{index + 1}</div></td>
|
||||||
|
<td className="flex flex-row justify-between items-center h-full min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]">
|
||||||
|
<div className="flex flex-row items-center h-8">{secret?.key || ''}</div>
|
||||||
|
<button type="button" className='mr-2 text-bunker-400 hover:text-bunker-300 invisible group-hover:visible' onClick={() => setAreValuesHiddenThisRow(!areValuesHiddenThisRow)}>
|
||||||
|
<FontAwesomeIcon icon={areValuesHiddenThisRow ? faEye : faEyeSlash} />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
{userAvailableEnvs?.map(env => {
|
||||||
|
return <DashboardInput key={`row-${secret?.key || ''}-${env.slug}`} isOverridden={false} isSecretValueHidden={areValuesHiddenThisRow && isSecretValueHidden} isReadOnly={isReadOnly} secret={secrets?.filter(sec => sec.env === env.slug)[0]} index={index} />
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { EnvComparisonRow } from './EnvComparisonRow';
|
||||||
@@ -156,7 +156,7 @@ export const SecretInputRow = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className="group min-w-full flex flex-row items-center">
|
<tr className="group min-w-full flex flex-row items-center" key={index}>
|
||||||
<td className="w-10 h-10 px-4 flex items-center justify-center"><div className='text-center w-10 text-xs text-bunker-400'>{index + 1}</div></td>
|
<td className="w-10 h-10 px-4 flex items-center justify-center"><div className='text-center w-10 text-xs text-bunker-400'>{index + 1}</div></td>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
@@ -279,7 +279,7 @@ export const SecretInputRow = ({
|
|||||||
{(isOverridden ? secret.valueOverride : secret.value)?.split(REGEX).map((word) => {
|
{(isOverridden ? secret.valueOverride : secret.value)?.split(REGEX).map((word) => {
|
||||||
if (word.match(REGEX) !== null) {
|
if (word.match(REGEX) !== null) {
|
||||||
return (
|
return (
|
||||||
<span className="ph-no-capture text-yellow" key={index}>
|
<span className="ph-no-capture text-yellow" key={guidGenerator()}>
|
||||||
{word.slice(0, 2)}
|
{word.slice(0, 2)}
|
||||||
<span className="ph-no-capture text-yellow-200/80">
|
<span className="ph-no-capture text-yellow-200/80">
|
||||||
{word.slice(2, word.length - 1)}
|
{word.slice(2, word.length - 1)}
|
||||||
|
|||||||
-1
@@ -64,7 +64,6 @@ export const SecretTagsSection = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onFormSubmit = async (data: CreateWsTag) => {
|
const onFormSubmit = async (data: CreateWsTag) => {
|
||||||
console.log(19191, data);
|
|
||||||
await onCreateTag(data);
|
await onCreateTag(data);
|
||||||
handlePopUpClose('CreateSecretTag');
|
handlePopUpClose('CreateSecretTag');
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-4
@@ -37,13 +37,14 @@ const apiTokenExpiry = [
|
|||||||
{ label: '7 Days', value: 604800 },
|
{ label: '7 Days', value: 604800 },
|
||||||
{ label: '1 Month', value: 2592000 },
|
{ label: '1 Month', value: 2592000 },
|
||||||
{ label: '6 months', value: 15552000 },
|
{ label: '6 months', value: 15552000 },
|
||||||
{ label: '12 months', value: 31104000 }
|
{ label: '12 months', value: 31104000 },
|
||||||
|
{ label: 'Never', value: null },
|
||||||
];
|
];
|
||||||
|
|
||||||
const createServiceTokenSchema = yup.object({
|
const createServiceTokenSchema = yup.object({
|
||||||
name: yup.string().required().label('Service Token Name'),
|
name: yup.string().required().label('Service Token Name'),
|
||||||
environment: yup.string().required().label('Environment'),
|
environment: yup.string().required().label('Environment'),
|
||||||
expiresIn: yup.string().required().label('Service Token Expiration'),
|
expiresIn: yup.string().optional().label('Service Token Expiration'),
|
||||||
permissions: yup.object().shape({
|
permissions: yup.object().shape({
|
||||||
read: yup.boolean().required(),
|
read: yup.boolean().required(),
|
||||||
write: yup.boolean().required()
|
write: yup.boolean().required()
|
||||||
@@ -213,7 +214,7 @@ export const ServiceTokenSection = ({
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{apiTokenExpiry.map(({ label, value }) => (
|
{apiTokenExpiry.map(({ label, value }) => (
|
||||||
<SelectItem value={String(value)} key={label}>
|
<SelectItem value={String(value || '')} key={label}>
|
||||||
{label}
|
{label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -332,7 +333,7 @@ export const ServiceTokenSection = ({
|
|||||||
<Tr key={row._id}>
|
<Tr key={row._id}>
|
||||||
<Td>{row.name}</Td>
|
<Td>{row.name}</Td>
|
||||||
<Td>{row.environment}</Td>
|
<Td>{row.environment}</Td>
|
||||||
<Td>{new Date(row.expiresAt).toUTCString()}</Td>
|
<Td>{row.expiresAt && new Date(row.expiresAt).toUTCString()}</Td>
|
||||||
<Td className="flex items-center justify-end">
|
<Td className="flex items-center justify-end">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user