Merge remote-tracking branch 'origin' into gcp-integration

This commit is contained in:
Tuan Dang
2023-08-25 00:35:11 +07:00
27 changed files with 478 additions and 152 deletions
+3
View File
@@ -3203,6 +3203,9 @@
"name": { "name": {
"example": "any" "example": "any"
}, },
"tagColor": {
"example": "any"
},
"slug": { "slug": {
"example": "any" "example": "any"
} }
@@ -234,6 +234,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
$inc: { $inc: {
version: 1 version: 1
}, },
$unset: {
'metadata.source': true as true
},
...u, ...u,
_id: new Types.ObjectId(u._id) _id: new Types.ObjectId(u._id)
} }
@@ -966,6 +969,7 @@ export const getSecrets = async (req: Request, res: Response) => {
); );
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: "secrets pulled", event: "secrets pulled",
+2 -1
View File
@@ -6,10 +6,11 @@ import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
export const createWorkspaceTag = async (req: Request, res: Response) => { export const createWorkspaceTag = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const { workspaceId } = req.params;
const { name, slug } = req.body; const { name, slug, tagColor } = req.body;
const tagToCreate = { const tagToCreate = {
name, name,
tagColor,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
slug, slug,
user: new Types.ObjectId(req.user._id), user: new Types.ObjectId(req.user._id),
+1 -1
View File
@@ -117,7 +117,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
ref: "Tag", ref: "Tag",
type: [Schema.Types.ObjectId], type: [Schema.Types.ObjectId],
default: [], default: [],
}, }
}, },
{ {
timestamps: true, timestamps: true,
+8 -5
View File
@@ -393,9 +393,10 @@ export const createSecretHelper = async ({
secretCommentTag, secretCommentTag,
folder: folderId, folder: folderId,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8 keyEncoding: ENCODING_SCHEME_UTF8,
metadata
}).save(); }).save();
const secretVersion = new SecretVersion({ const secretVersion = new SecretVersion({
secret: secret._id, secret: secret._id,
version: secret.version, version: secret.version,
@@ -415,7 +416,7 @@ export const createSecretHelper = async ({
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8 keyEncoding: ENCODING_SCHEME_UTF8
}); });
// (EE) add version for new secret // (EE) add version for new secret
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: [secretVersion] secretVersions: [secretVersion]
@@ -566,15 +567,17 @@ export const getSecretsHelper = async ({
); );
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const numberOfSignupSecrets = (secrets.filter((secret) => secret?.metadata?.source === "signup")).length;
if (postHogClient) { if (postHogClient && (secrets.length - numberOfSignupSecrets > 0)) {
postHogClient.capture({ postHogClient.capture({
event: "secrets pulled", event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData authData
}), }),
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length - numberOfSignupSecrets,
environment, environment,
workspaceId, workspaceId,
folderId, folderId,
+6
View File
@@ -31,6 +31,9 @@ export interface ISecret {
keyEncoding: "utf8" | "base64"; keyEncoding: "utf8" | "base64";
tags?: string[]; tags?: string[];
folder?: string; folder?: string;
metadata?: {
[key: string]: string;
}
} }
const secretSchema = new Schema<ISecret>( const secretSchema = new Schema<ISecret>(
@@ -131,6 +134,9 @@ const secretSchema = new Schema<ISecret>(
type: String, type: String,
default: "root", default: "root",
}, },
metadata: {
type: Schema.Types.Mixed
}
}, },
{ {
timestamps: true, timestamps: true,
+6
View File
@@ -3,6 +3,7 @@ import { Schema, Types, model } from "mongoose";
export interface ITag { export interface ITag {
_id: Types.ObjectId; _id: Types.ObjectId;
name: string; name: string;
tagColor: string;
slug: string; slug: string;
user: Types.ObjectId; user: Types.ObjectId;
workspace: Types.ObjectId; workspace: Types.ObjectId;
@@ -15,6 +16,11 @@ const tagSchema = new Schema<ITag>(
required: true, required: true,
trim: true, trim: true,
}, },
tagColor: {
type: String,
required: false,
trim: true,
},
slug: { slug: {
type: String, type: String,
required: true, required: true,
+1
View File
@@ -48,6 +48,7 @@ router.post(
}), }),
param("workspaceId").exists().trim(), param("workspaceId").exists().trim(),
body("name").exists().trim(), body("name").exists().trim(),
body("tagColor").exists().trim(),
body("slug").exists().trim(), body("slug").exists().trim(),
validateRequest, validateRequest,
tagController.createWorkspaceTag tagController.createWorkspaceTag
@@ -111,14 +111,14 @@ frontend:
replicaCount: 2 replicaCount: 2
image: image:
repository: infisical/frontend repository: infisical/frontend
tag: "v0.1.3" tag: "v0.26.0" # <--- frontend version
pullPolicy: Always pullPolicy: Always
backend: backend:
replicaCount: 2 replicaCount: 2
image: image:
repository: infisical/backend repository: infisical/backend
tag: "v0.1.3" tag: "v0.26.0" # <--- backend version
pullPolicy: Always pullPolicy: Always
backendEnvironmentVariables: backendEnvironmentVariables:
@@ -126,7 +126,7 @@ backendEnvironmentVariables:
ingress: ingress:
nginx: nginx:
enabled: false #<-- if you would like to install nginx along with Infisical enabled: true #<-- if you would like to install nginx along with Infisical
``` ```
@@ -217,4 +217,4 @@ Once installation is complete, you will have to create the first account. No def
</Info> </Info>
## Related blogs ## Related blogs
- [Set up Infisical in a development cluster](https://iamunnip.hashnode.dev/infisical-open-source-secretops-kubernetes-setup) - [Set up Infisical in a development cluster](https://iamunnip.hashnode.dev/infisical-open-source-secretops-kubernetes-setup)
+2
View File
@@ -1949,6 +1949,8 @@ paths:
properties: properties:
name: name:
example: any example: any
tagColor:
example: any
slug: slug:
example: any example: any
/api/v2/workspace/tags/{tagId}: /api/v2/workspace/tags/{tagId}:
@@ -0,0 +1,78 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Checkbox, PopoverContent } from "@app/components/v2";
import { WsTag } from "../../hooks/api/tags/types";
interface Props {
wsTags: WsTag[] | undefined;
secKey: string;
selectedTagIds: Record<string, boolean>;
handleSelectTag: (wsTag: WsTag) => void;
handleTagOnMouseEnter: (wsTag: WsTag) => void;
handleTagOnMouseLeave: () => void;
checkIfTagIsVisible: (wsTag: WsTag) => boolean;
handleOnCreateTagOpen: () => void
}
const AddTagPopoverContent = ({
wsTags,
secKey,
selectedTagIds,
handleSelectTag,
handleTagOnMouseEnter,
handleTagOnMouseLeave,
checkIfTagIsVisible,
handleOnCreateTagOpen
}: Props) => {
return (
<PopoverContent
side="left"
className="relative max-h-96 w-auto min-w-[200px] p-2 overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 text-bunker-200"
hideCloseBtn
>
<div className=" text-center text-sm font-medium text-bunker-200">
Add tags to {secKey || "this secret"}
</div>
<div className="absolute left-0 w-full border-mineshaft-600 border-t mt-2" />
<div className="flex flex-col space-y-1.5">
{wsTags?.map((wsTag: WsTag) => (
<div key={`tag-${wsTag._id}`} className="mt-4 h-[32px] relative flex items-center justify-start hover:border-mineshaft-600 hover:border hover:bg-mineshaft-700 p-2 rounded-md hover:text-bunker-200 bg-none"
onClick={() => handleSelectTag(wsTag)}
onMouseEnter={() => handleTagOnMouseEnter(wsTag)}
onMouseLeave={() => handleTagOnMouseLeave()}
tabIndex={0} role="button"
onKeyDown={() => { }}>
{
(checkIfTagIsVisible(wsTag) || selectedTagIds?.[wsTag.slug]) && <Checkbox
id="autoCapitalization"
isChecked={selectedTagIds?.[wsTag.slug]}
className="absolute top-[50%] translate-y-[-50%] left-[10px] "
checkIndicatorBg={`${!selectedTagIds?.[wsTag.slug] ? "text-transparent" : "text-mineshaft-800"}`}
/>
}
<div className="ml-7 flex items-center gap-3">
<div className="w-[10px] h-[10px] rounded-full" style={{ background: wsTag?.tagColor ? wsTag.tagColor : "#bec2c8" }}> </div>
<span >
{wsTag.slug}
</span>
</div>
</div>
))}
<div
className="h-[32px] relative flex items-center cursor-pointer justify-start border-mineshaft-600 border bg-mineshaft-700 p-2 rounded-md hover:text-bunker-200 bg-none"
onClick={() => handleOnCreateTagOpen()}
tabIndex={0} role="button"
onKeyDown={() => { }}>
<FontAwesomeIcon icon={faPlus} className="ml-1 mr-2" />
<span> Add new tag</span>
</div>
</div>
</PopoverContent>
)
}
export default AddTagPopoverContent
@@ -0,0 +1,5 @@
export const isValidHexColor = (hexColor: string) => {
const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
return hexColorPattern.test(hexColor);
}
@@ -8,11 +8,12 @@ export type CheckboxProps = Omit<
CheckboxPrimitive.CheckboxProps, CheckboxPrimitive.CheckboxProps,
"checked" | "disabled" | "required" "checked" | "disabled" | "required"
> & { > & {
children: ReactNode; children?: ReactNode;
id: string; id: string;
isDisabled?: boolean; isDisabled?: boolean;
isChecked?: boolean; isChecked?: boolean;
isRequired?: boolean; isRequired?: boolean;
checkIndicatorBg?: string | undefined;
}; };
export const Checkbox = ({ export const Checkbox = ({
@@ -22,6 +23,7 @@ export const Checkbox = ({
isChecked, isChecked,
isDisabled, isDisabled,
isRequired, isRequired,
checkIndicatorBg,
...props ...props
}: CheckboxProps): JSX.Element => { }: CheckboxProps): JSX.Element => {
return ( return (
@@ -39,7 +41,7 @@ export const Checkbox = ({
{...props} {...props}
id={id} id={id}
> >
<CheckboxPrimitive.Indicator className="text-bunker-800"> <CheckboxPrimitive.Indicator className={`${checkIndicatorBg || "text-bunker-800"}`}>
<FontAwesomeIcon icon={faCheck} size="sm" /> <FontAwesomeIcon icon={faCheck} size="sm" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
+2 -22
View File
@@ -1,19 +1,14 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { faClose } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { cva, VariantProps } from "cva"; import { cva, VariantProps } from "cva";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
type Props = { type Props = {
children: ReactNode; children: ReactNode;
className?: string; className?: string;
onClose?: () => void;
color?: string;
isDisabled?: boolean;
} & VariantProps<typeof tagVariants>; } & VariantProps<typeof tagVariants>;
const tagVariants = cva( const tagVariants = cva(
"inline-flex items-center whitespace-nowrap text-sm rounded-sm mr-1.5 text-bunker-200", "inline-flex items-center whitespace-nowrap text-sm rounded-sm mr-1.5 text-bunker-200 rounded-[30px] text-gray-400 ",
{ {
variants: { variants: {
colorSchema: { colorSchema: {
@@ -32,25 +27,10 @@ export const Tag = ({
children, children,
className, className,
colorSchema = "gray", colorSchema = "gray",
color, size = "sm" }: Props) => (
isDisabled,
size = "sm",
onClose
}: Props) => (
<div <div
className={twMerge(tagVariants({ colorSchema, className, size }))} className={twMerge(tagVariants({ colorSchema, className, size }))}
style={{ backgroundColor: color }}
> >
{children} {children}
{onClose && (
<button
type="button"
onClick={onClose}
disabled={isDisabled}
className="ml-2 flex items-center justify-center"
>
<FontAwesomeIcon icon={faClose} />
</button>
)}
</div> </div>
); );
+66
View File
@@ -51,3 +51,69 @@ const plansProd: Mapping = {
export const plans = plansProd || plansDev; export const plans = plansProd || plansDev;
export const leaveConfirmDefaultMessage = "Your changes will be lost if you leave the page. Are you sure you want to continue?"; export const leaveConfirmDefaultMessage = "Your changes will be lost if you leave the page. Are you sure you want to continue?";
export const secretTagsColors = [
{
id: 1,
hex: "#bec2c8",
rgba: "rgb(128,128,128, 0.8)",
name: "Grey",
selected: true
},
{
id: 2,
hex: "#95a2b3",
rgba: "rgb(0,0,255, 0.8)",
name: "blue",
selected: false
},
{
id: 3,
hex: "#5e6ad2",
rgba: "rgb(128,0,128, 0.8)",
name: "Purple",
selected: false
},
{
id: 4,
hex: "#26b5ce",
rgba: "rgb(0,128,128, 0.8)",
name: "Teal",
selected: false
},
{
id: 5,
hex: "#4cb782",
rgba: "rgb(0,128,0, 0.8)",
name: "Green",
selected: false
},
{
id: 6,
hex: "#f2c94c",
rgba: "rgb(255,255,0, 0.8)",
name: "Yellow",
selected: false
},
{
id: 7,
hex: "#f2994a",
rgba: "rgb(128,128,0, 0.8)",
name: "Orange",
selected: false
},
{
id: 8,
hex: "#f7c8c1",
rgba: "rgb(128,0,0, 0.8)",
name: "Pink",
selected: false
},
{
id: 9,
hex: "#eb5757",
rgba: "rgb(255,0,0, 0.8)",
name: "Red",
selected: false
},
]
+2 -1
View File
@@ -38,7 +38,7 @@ const fetchProjectEncryptedSecrets = async (
folderId?: string, folderId?: string,
secretPath?: string secretPath?: string
) => { ) => {
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>("/api/v2/secrets", { const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>("/api/v3/secrets", {
params: { params: {
environment: env, environment: env,
workspaceId, workspaceId,
@@ -46,6 +46,7 @@ const fetchProjectEncryptedSecrets = async (
secretPath secretPath
} }
}); });
return data.secrets; return data.secrets;
}; };
+5 -2
View File
@@ -7,7 +7,7 @@ import {
CreateTagRes, CreateTagRes,
DeleteTagDTO, DeleteTagDTO,
DeleteWsTagRes, DeleteWsTagRes,
UserWsTags UserWsTags,
} from "./types"; } from "./types";
const workspaceTags = { const workspaceTags = {
@@ -30,13 +30,15 @@ export const useGetWsTags = (workspaceID: string) => {
}); });
} }
export const useCreateWsTag = () => { export const useCreateWsTag = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<CreateTagRes, {}, CreateTagDTO>({ return useMutation<CreateTagRes, {}, CreateTagDTO>({
mutationFn: async ({ workspaceID, tagName, tagSlug }) => { mutationFn: async ({ workspaceID, tagName, tagColor, tagSlug }) => {
const { data } = await apiRequest.post(`/api/v2/workspace/${workspaceID}/tags`, { const { data } = await apiRequest.post(`/api/v2/workspace/${workspaceID}/tags`, {
name: tagName, name: tagName,
tagColor: tagColor || "",
slug: tagSlug slug: tagSlug
}) })
return data; return data;
@@ -47,6 +49,7 @@ export const useCreateWsTag = () => {
}); });
}; };
export const useDeleteWsTag = () => { export const useDeleteWsTag = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
+19 -1
View File
@@ -4,6 +4,7 @@ export type WsTag = {
_id: string; _id: string;
name: string; name: string;
slug: string; slug: string;
tagColor?: string;
workspace: string; workspace: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
@@ -16,6 +17,7 @@ export type CreateTagDTO = {
workspaceID: string; workspaceID: string;
tagSlug: string; tagSlug: string;
tagName: string; tagName: string;
tagColor: string;
}; };
export type CreateTagRes = { export type CreateTagRes = {
@@ -23,6 +25,7 @@ export type CreateTagRes = {
slug: string; slug: string;
workspace: string; workspace: string;
createdAt: string; createdAt: string;
tagColor?: string;
user: string; user: string;
_id: string; _id: string;
}; };
@@ -36,4 +39,19 @@ export type DeleteWsTagRes = {
createdAt: string; createdAt: string;
user: string; user: string;
_id: string; _id: string;
}; };
export type SecretTags = {
id: string;
_id: string;
slug: string;
tagColor: string;
}
export type TagColor = {
id: number;
hex: string
rgba: string
name: string
selected: boolean
}
+25
View File
@@ -107,6 +107,31 @@
@apply bg-primary-400; @apply bg-primary-400;
} }
} }
.tags-conic-bg {
background: conic-gradient(rgb(235, 87, 87), rgb(242, 201, 76), rgb(76, 183, 130), rgb(78, 167, 252), rgb(250, 96, 122));
}
.show-tags {
transform: translateY(10px);
transition: all 0.2s;
opacity: 1;
}
.hide-tags {
transform: translateY(-20px);
transition: all 0.2s;
opacity: 0;
}
.show-hex-input {
transform: translateY(-33px);
transition: all 0.2s;
opacity: 1;
}
.hide-hex-input {
transform: translateY(20px);
transition: all 0.2s;
opacity: 0;
}
@import "@fontsource/inter/400.css"; @import "@fontsource/inter/400.css";
@import "@fontsource/inter/500.css"; @import "@fontsource/inter/500.css";
@@ -297,6 +297,7 @@ export const DashboardPage = () => {
resolver: yupResolver(schema) resolver: yupResolver(schema)
}); });
const { const {
register, register,
control, control,
@@ -513,11 +514,12 @@ export const DashboardPage = () => {
}, []); }, []);
const onCreateWsTag = useCallback( const onCreateWsTag = useCallback(
async (tagName: string) => { async (tagName: string, tagColor: string) => {
try { try {
await createWsTag({ await createWsTag({
workspaceID: workspaceId, workspaceID: workspaceId,
tagName, tagName,
tagColor,
tagSlug: tagName.replace(" ", "_") tagSlug: tagName.replace(" ", "_")
}); });
handlePopUpClose("addTag"); handlePopUpClose("addTag");
@@ -58,7 +58,8 @@ const secretSchema = yup.object({
yup.object({ yup.object({
_id: yup.string().required(), _id: yup.string().required(),
name: yup.string().required(), name: yup.string().required(),
slug: yup.string().required() slug: yup.string().required(),
tagColor: yup.string().nullable(),
}) })
), ),
overrideAction: yup.string().notRequired().oneOf(Object.values(SecretActionType)), overrideAction: yup.string().notRequired().oneOf(Object.values(SecretActionType)),
@@ -1,11 +1,21 @@
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import {
faCheck
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup"; import * as yup from "yup";
import { Button, FormControl, Input, ModalClose } from "@app/components/v2"; import { Button, FormControl, Input, ModalClose, Tooltip } from "@app/components/v2";
import { isValidHexColor } from "../../../../components/utilities/isValidHexColor";
import { secretTagsColors } from "../../../../const"
import { TagColor } from "../../../../hooks/api/tags/types";
type Props = { type Props = {
onCreateTag: (tagName: string) => Promise<void>; onCreateTag: (tagName: string, tagColor: string) => Promise<void>;
}; };
const createTagSchema = yup.object({ const createTagSchema = yup.object({
@@ -23,11 +33,62 @@ export const CreateTagModal = ({ onCreateTag }: Props): JSX.Element => {
resolver: yupResolver(createTagSchema) resolver: yupResolver(createTagSchema)
}); });
const [tagsColors] = useState<TagColor[]>(secretTagsColors)
const [selectedTagColor, setSelectedTagColor] = useState<TagColor>(tagsColors[0])
const [showHexInput, setShowHexInput] = useState<boolean>(false)
const [tagColor, setTagColor] = useState<string>("")
const onFormSubmit = async ({ name }: FormData) => { const onFormSubmit = async ({ name }: FormData) => {
await onCreateTag(name); await onCreateTag(name, tagColor);
reset(); reset();
}; };
useEffect(() => {
const clonedTagColors = [...tagsColors]
const selectedTagBgColor = clonedTagColors.find($tagColor => $tagColor.selected);
if (selectedTagBgColor) {
setSelectedTagColor(selectedTagBgColor);
setTagColor(selectedTagBgColor.hex);
}
}, [])
useEffect(() => {
const tagsList = document.querySelector(".secret-tags-wrapper")
const tagsHexWrapper = document.querySelector(".tags-hex-wrapper")
if (showHexInput) {
tagsList?.classList.add("hide-tags")
tagsList?.classList.remove("show-tags")
tagsHexWrapper?.classList.add("show-hex-input")
tagsHexWrapper?.classList.remove("hide-hex-input")
} else {
tagsList?.classList.remove("hide-tags")
tagsList?.classList.add("show-tags")
tagsHexWrapper?.classList.remove("show-hex-input")
tagsHexWrapper?.classList.add("hide-hex-input")
}
}, [showHexInput])
const handleColorChange = (clickedTagColor: TagColor) => {
const updatedTagColors = [...tagsColors];
const clickedTagColorIndex = updatedTagColors.findIndex(($tagColor) => $tagColor.id === clickedTagColor.id);
const updatedClickedTagColor = updatedTagColors[clickedTagColorIndex];
updatedTagColors.forEach((tgColor) => {
// eslint-disable-next-line no-param-reassign
tgColor.selected = false;
});
if (selectedTagColor.id !== clickedTagColor.id) {
updatedClickedTagColor.selected = !updatedClickedTagColor.selected;
setSelectedTagColor(updatedClickedTagColor);
setTagColor(updatedClickedTagColor.hex);
}
};
return ( return (
<form onSubmit={handleSubmit(onFormSubmit)}> <form onSubmit={handleSubmit(onFormSubmit)}>
<Controller <Controller
@@ -40,6 +101,81 @@ export const CreateTagModal = ({ onCreateTag }: Props): JSX.Element => {
</FormControl> </FormControl>
)} )}
/> />
<div className="mt-2">
<div className="mb-0.5 ml-1 block text-sm font-normal text-mineshaft-400">Tag Color</div>
<div className="flex gap-2 h-[50px]">
<div className="w-[12%] h-[2.813rem] inline-flex font-inter items-center justify-center border relative rounded-md border-mineshaft-500 bg-mineshaft-900 hover:bg-mineshaft-800">
<div className="w-[26px] h-[26px] rounded-full" style={{ background: `${tagColor}` }} />
</div>
<div className="w-[88%] h-[2.813rem] flex-wrap inline-flex gap-3 items-center border rounded-md border-mineshaft-500 bg-mineshaft-900 hover:bg-mineshaft-800 relative">
<div className="flex-wrap inline-flex gap-3 items-center secret-tags-wrapper pl-3">
{
tagsColors.map(($tagColor: TagColor) => {
return (
<div key={`tag-color-${$tagColor.id}`}>
<Tooltip content={`${$tagColor.name}`}>
<div className=" flex items-center justify-center w-[26px] h-[26px] hover:ring-offset-2 hover:ring-2 bg-[#bec2c8] border-2 p-2 hover:shadow-lg border-transparent hover:border-black rounded-full"
key={`tag-${$tagColor.id}`}
style={{ backgroundColor: `${$tagColor.hex}` }}
onClick={() => handleColorChange($tagColor)}
tabIndex={0} role="button"
onKeyDown={() => { }}
>
{
$tagColor.selected && <FontAwesomeIcon icon={faCheck} style={{ color: "#00000070" }} />
}
</div>
</Tooltip>
</div>
)
})
}
</div>
<div className="flex items-center gap-2 px-2 tags-hex-wrapper" >
<div className="w-1/6 flex items-center relative rounded-md hover:bg-mineshaft-800">
{
isValidHexColor(tagColor) && (
<div className="w-[26px] h-[26px] rounded-full flex items-center justify-center" style={{ background: `${tagColor}` }}>
<FontAwesomeIcon icon={faCheck} style={{ color: "#00000070" }} />
</div>
)
}
{
!isValidHexColor(tagColor) && (
<div className="border-dashed border bg-blue rounded-full w-[26px] h-[26px] border-mineshaft-500" />
)
}
</div>
<div className="w-10/12">
<Input
variant="plain"
className="w-full focus:text-bunker-100 focus:ring-transparent bg-transparent"
autoCapitalization={false}
value={tagColor}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTagColor(e.target.value)}
/>
</div>
</div>
<div className="w-[26px] h-[26px] flex items-center justify-center absolute top-[10px] right-[-4px] translate-x-[-50%]">
<div className="border-mineshaft-500 border h-[2.1rem] mr-4 absolute right-5" />
<div className={`flex items-center justify-center w-[26px] h-[26px] bg-transparent cursor-pointer hover:ring-offset-1 hover:ring-2 border-mineshaft-500 border bg-mineshaft-900 rounded-[3px] p-2 ${showHexInput ? "tags-conic-bg rounded-full" : ""}`} onClick={() => setShowHexInput((prev) => !prev)} style={{ border: "1px solid rgba(220, 216, 254, 0.376)" }}
tabIndex={0} role="button"
onKeyDown={() => { }}>
{
!showHexInput && <span>#</span>
}
</div>
</div>
</div>
</div>
</div>
<div className="mt-8 flex items-center"> <div className="mt-8 flex items-center">
<Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}> <Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
Create Create
@@ -1,5 +1,5 @@
/* eslint-disable react/jsx-no-useless-fragment */ /* eslint-disable react/jsx-no-useless-fragment */
import { memo, useEffect, useRef, useState } from "react"; import { memo, useEffect,useRef, useState } from "react";
import { import {
Control, Control,
Controller, Controller,
@@ -15,7 +15,6 @@ import {
faCopy, faCopy,
faEllipsis, faEllipsis,
faInfoCircle, faInfoCircle,
faPlus,
faTags, faTags,
faXmark faXmark
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
@@ -24,38 +23,22 @@ import { cx } from "cva";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { import {
Button,
Checkbox,
FormControl,
HoverCard, HoverCard,
HoverCardContent, HoverCardContent,
HoverCardTrigger, HoverCardTrigger,
IconButton, IconButton,
Input, Input,
Popover, Popover,
PopoverContent,
PopoverTrigger, PopoverTrigger,
SecretInput, SecretInput,
Tag, Tag,
TextArea, Tooltip} from "@app/components/v2";
Tooltip
} from "@app/components/v2";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { WsTag } from "@app/hooks/api/types"; import { WsTag } from "@app/hooks/api/types";
import AddTagPopoverContent from "../../../../components/AddTagPopoverContent/AddTagPopoverContent";
import { FormData, SecretActionType } from "../../DashboardPage.utils"; import { FormData, SecretActionType } from "../../DashboardPage.utils";
const tagColors = [
{ bg: "bg-[#f1c40f]/40", text: "text-[#fcf0c3]/70" },
{ bg: "bg-[#cb1c8d]/40", text: "text-[#f2c6e3]/70" },
{ bg: "bg-[#badc58]/40", text: "text-[#eef6d5]/70" },
{ bg: "bg-[#ff5400]/40", text: "text-[#ffddcc]/70" },
{ bg: "bg-[#3AB0FF]/40", text: "text-[#f0fffd]/70" },
{ bg: "bg-[#6F1AB6]/40", text: "text-[#FFE5F1]/70" },
{ bg: "bg-[#C40B13]/40", text: "text-[#FFDEDE]/70" },
{ bg: "bg-[#332FD0]/40", text: "text-[#DFF6FF]/70" }
];
type Props = { type Props = {
index: number; index: number;
// backend generated unique id // backend generated unique id
@@ -95,12 +78,12 @@ export const SecretInputRow = memo(
onSecretDelete, onSecretDelete,
searchTerm, searchTerm,
control, control,
register, // register,
setValue, setValue,
isKeyError, isKeyError,
keyError, keyError,
secUniqId, secUniqId,
autoCapitalization autoCapitalization,
}: Props): JSX.Element => { }: Props): JSX.Element => {
const isKeySubDisabled = useRef<boolean>(false); const isKeySubDisabled = useRef<boolean>(false);
// comment management in a row // comment management in a row
@@ -110,10 +93,8 @@ export const SecretInputRow = memo(
append append
} = useFieldArray({ control, name: `secrets.${index}.tags` }); } = useFieldArray({ control, name: `secrets.${index}.tags` });
const tagColorByTagId = new Map((wsTags || []).map((wsTag, i) => [wsTag._id, tagColors[i % tagColors.length]]))
// display the tags in alphabetical order // display the tags in alphabetical order
secretTags.sort((a, b) => a.name.localeCompare(b.name)) secretTags.sort((a, b) => a?.name?.localeCompare(b?.name))
// to get details on a secret // to get details on a secret
const overrideAction = useWatch({ const overrideAction = useWatch({
@@ -145,11 +126,24 @@ export const SecretInputRow = memo(
// when secret is override by personal values // when secret is override by personal values
const isOverridden = const isOverridden =
overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified; overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified;
const [editorRef, setEditorRef] = useState(isOverridden ? secValueOverride : secValue); const [editorRef, setEditorRef] = useState(isOverridden ? secValueOverride : secValue);
const [hoveredTag, setHoveredTag] = useState<WsTag | null>(null);
const handleTagOnMouseEnter = (wsTag: WsTag) => {
setHoveredTag(wsTag);
}
const handleTagOnMouseLeave = () => {
setHoveredTag(null);
}
const checkIfTagIsVisible = (wsTag: WsTag) => wsTag._id === hoveredTag?._id;
const secId = useWatch({ control, name: `secrets.${index}._id`, exact: true }); const secId = useWatch({ control, name: `secrets.${index}._id`, exact: true });
const tags = const tags = useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || [];
useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || [];
const selectedTagIds = tags.reduce<Record<string, boolean>>( const selectedTagIds = tags.reduce<Record<string, boolean>>(
(prev, curr) => ({ ...prev, [curr.slug]: true }), (prev, curr) => ({ ...prev, [curr.slug]: true }),
{} {}
@@ -157,6 +151,7 @@ export const SecretInputRow = memo(
const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false); const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false);
useEffect(() => { useEffect(() => {
let timer: NodeJS.Timeout; let timer: NodeJS.Timeout;
if (isInviteLinkCopied) { if (isInviteLinkCopied) {
@@ -165,6 +160,7 @@ export const SecretInputRow = memo(
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [isInviteLinkCopied]); }, [isInviteLinkCopied]);
useEffect(() => { useEffect(() => {
setEditorRef(isOverridden ? secValueOverride : secValue); setEditorRef(isOverridden ? secValueOverride : secValue);
}, [isOverridden]); }, [isOverridden]);
@@ -195,13 +191,13 @@ export const SecretInputRow = memo(
const onSelectTag = (selectedTag: WsTag) => { const onSelectTag = (selectedTag: WsTag) => {
const shouldAppend = !selectedTagIds[selectedTag.slug]; const shouldAppend = !selectedTagIds[selectedTag.slug];
if (shouldAppend) { if (shouldAppend) {
append(selectedTag); const {_id: id, name, slug, tagColor} = selectedTag
append({_id: id, name, slug, tagColor});
} else { } else {
const pos = tags.findIndex(({ slug }) => selectedTag.slug === slug); const pos = tags.findIndex(({ slug }: { slug: string }) => selectedTag.slug === slug);
remove(pos); remove(pos);
} }
}; };
const isCreatedSecret = !secId; const isCreatedSecret = !secId;
const shouldBeBlockedInAddOnly = !isCreatedSecret && isAddOnly; const shouldBeBlockedInAddOnly = !isCreatedSecret && isAddOnly;
@@ -228,6 +224,7 @@ export const SecretInputRow = memo(
<td className="flex h-10 w-10 items-center justify-center border-none px-4"> <td className="flex h-10 w-10 items-center justify-center border-none px-4">
<div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div> <div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div>
</td> </td>
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
@@ -326,21 +323,38 @@ export const SecretInputRow = memo(
</td> </td>
<td className="min-w-sm flex"> <td className="min-w-sm flex">
<div className="flex h-8 items-center pl-2"> <div className="flex h-8 items-center pl-2">
{secretTags.map(({ id, _id, slug }, i) => { {secretTags.map(({ id, slug, tagColor}) => {
// This map lookup shouldn't ever fail, but if it does we default to the first color
const tagColor = tagColorByTagId.get(_id) || tagColors[0]
return ( return (
<Tag <>
className={cx( <Popover>
tagColor.bg, <PopoverTrigger asChild>
tagColor.text <div>
)} <Tag
isDisabled={isReadOnly || isAddOnly || isRollbackMode} // isDisabled={isReadOnly || isAddOnly || isRollbackMode}
onClose={() => remove(i)} // onClose={() => remove(i)}
key={id} key={id}
> className="cursor-pointer"
{slug} >
</Tag>) <div className="rounded-full border-mineshaft-500 bg-transparent flex items-center gap-1.5 justify-around">
<div className="w-[10px] h-[10px] rounded-full" style={{ background: tagColor || "#bec2c8" }} />
{slug}
</div>
</Tag>
</div>
</PopoverTrigger>
<AddTagPopoverContent
wsTags={wsTags}
secKey={secKey || "this secret"}
selectedTagIds={selectedTagIds}
handleSelectTag={(wsTag: WsTag) => onSelectTag(wsTag)}
handleTagOnMouseEnter={(wsTag: WsTag) => handleTagOnMouseEnter(wsTag)}
handleTagOnMouseLeave={() => handleTagOnMouseLeave()}
checkIfTagIsVisible={(wsTag: WsTag) => checkIfTagIsVisible(wsTag)}
handleOnCreateTagOpen={() => onCreateTagOpen()}
/>
</Popover>
</>
)
})} })}
<div className="w-0 overflow-hidden group-hover:w-6"> <div className="w-0 overflow-hidden group-hover:w-6">
<Tooltip content="Copy value"> <Tooltip content="Copy value">
@@ -372,51 +386,16 @@ export const SecretInputRow = memo(
</Tooltip> </Tooltip>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <AddTagPopoverContent
side="left" wsTags={wsTags}
className="max-h-96 w-auto min-w-[200px] overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 p-2 text-bunker-200" secKey={secKey || "this secret"}
hideCloseBtn selectedTagIds={selectedTagIds}
> handleSelectTag={(wsTag: WsTag) => onSelectTag(wsTag)}
<div className="mb-2 px-2 text-center text-sm font-medium text-bunker-200"> handleTagOnMouseEnter={(wsTag: WsTag) => handleTagOnMouseEnter(wsTag)}
Add tags to {secKey || "this secret"} handleTagOnMouseLeave={() => handleTagOnMouseLeave()}
</div> checkIfTagIsVisible={(wsTag: WsTag) => checkIfTagIsVisible(wsTag)}
<div className="flex flex-col space-y-1"> handleOnCreateTagOpen={() => onCreateTagOpen()}
{wsTags?.map((wsTag) => ( />
<Button
variant="plain"
size="sm"
className={twMerge(
"justify-start bg-mineshaft-600 text-bunker-100 hover:bg-mineshaft-500",
selectedTagIds?.[wsTag.slug] && "text-primary"
)}
onClick={() => onSelectTag(wsTag)}
leftIcon={
<Checkbox
className="mr-0 data-[state=checked]:bg-primary"
id="autoCapitalization"
isChecked={selectedTagIds?.[wsTag.slug]}
onCheckedChange={() => {}}
>
{}
</Checkbox>
}
key={wsTag._id}
>
{wsTag.slug}
</Button>
))}
<Button
variant="star"
color="primary"
size="sm"
className="mt-4 h-7 justify-start bg-mineshaft-600 px-1"
onClick={onCreateTagOpen}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Add new tag
</Button>
</div>
</PopoverContent>
</Popover> </Popover>
</div> </div>
)} )}
@@ -460,20 +439,16 @@ export const SecretInputRow = memo(
<FontAwesomeIcon icon={faComment} /> <FontAwesomeIcon icon={faComment} />
</IconButton> </IconButton>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <AddTagPopoverContent
className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl" wsTags={wsTags}
sticky="always" secKey={secKey || "this secret"}
> selectedTagIds={selectedTagIds}
<FormControl label="Comment" className="mb-0"> handleSelectTag={(wsTag: WsTag) => onSelectTag(wsTag)}
<TextArea handleTagOnMouseEnter={(wsTag: WsTag) => handleTagOnMouseEnter(wsTag)}
isDisabled={isReadOnly || isRollbackMode || shouldBeBlockedInAddOnly} handleTagOnMouseLeave={() => handleTagOnMouseLeave()}
className="border border-mineshaft-600 text-sm" checkIfTagIsVisible={(wsTag: WsTag) => checkIfTagIsVisible(wsTag)}
{...register(`secrets.${index}.comment`)} handleOnCreateTagOpen={() => onCreateTagOpen()}
rows={8} />
cols={30}
/>
</FormControl>
</PopoverContent>
</Popover> </Popover>
</div> </div>
</Tooltip> </Tooltip>
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { faFolderBlank, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons"; import { faArrowDown, faArrowUp, faFolderBlank, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@@ -10,6 +10,7 @@ import NavHeader from "@app/components/navigation/NavHeader";
import { import {
Button, Button,
EmptyState, EmptyState,
IconButton,
Input, Input,
Table, Table,
TableContainer, TableContainer,
@@ -46,6 +47,7 @@ export const SecretOverviewPage = () => {
// coz when overflow the table goes to the right // coz when overflow the table goes to the right
const parentTableRef = useRef<HTMLTableElement>(null); const parentTableRef = useRef<HTMLTableElement>(null);
const [expandableTableWidth, setExpandableTableWidth] = useState(0); const [expandableTableWidth, setExpandableTableWidth] = useState(0);
const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
useEffect(() => { useEffect(() => {
const handleParentTableWidthResize = () => { const handleParentTableWidthResize = () => {
@@ -219,7 +221,7 @@ export const SecretOverviewPage = () => {
const filteredSecretNames = secKeys?.filter((name) => const filteredSecretNames = secKeys?.filter((name) =>
name.toUpperCase().includes(searchFilter.toUpperCase()) name.toUpperCase().includes(searchFilter.toUpperCase())
); ).sort((a, b) => sortDir === "asc" ? a.localeCompare(b) : b.localeCompare(a));
const filteredFolderNames = folderNames?.filter((name) => const filteredFolderNames = folderNames?.filter((name) =>
name.toLowerCase().includes(searchFilter.toLowerCase()) name.toLowerCase().includes(searchFilter.toLowerCase())
); );
@@ -278,6 +280,9 @@ export const SecretOverviewPage = () => {
<Th className="sticky left-0 z-20 min-w-[20rem] border-b-0 p-0"> <Th className="sticky left-0 z-20 min-w-[20rem] border-b-0 p-0">
<div className="flex items-center border-b border-r border-mineshaft-600 px-5 pt-4 pb-3.5"> <div className="flex items-center border-b border-r border-mineshaft-600 px-5 pt-4 pb-3.5">
Name Name
<IconButton variant="plain" className="ml-2" ariaLabel="sort" onClick={() => setSortDir(prev => prev === "asc" ? "desc" : "asc")}>
<FontAwesomeIcon icon={sortDir === "asc" ? faArrowDown : faArrowUp} />
</IconButton>
</div> </div>
</Th> </Th>
{userAvailableEnvs?.map(({ name, slug }, index) => { {userAvailableEnvs?.map(({ name, slug }, index) => {
@@ -53,7 +53,8 @@ export const AddSecretTagModal = ({
await createWsTag.mutateAsync({ await createWsTag.mutateAsync({
workspaceID: currentWorkspace?._id, workspaceID: currentWorkspace?._id,
tagName: name, tagName: name,
tagSlug: name.replace(" ", "_") tagSlug: name.replace(/\s+/g, " ").replace(" ", "_"),
tagColor: ""
}); });
handlePopUpClose("CreateSecretTag"); handlePopUpClose("CreateSecretTag");
@@ -62,6 +63,7 @@ export const AddSecretTagModal = ({
text: "Successfully created a tag", text: "Successfully created a tag",
type: "success" type: "success"
}); });
reset()
} catch (err) { } catch (err) {
console.error(err); console.error(err);
createNotification({ createNotification({
+1 -1
View File
@@ -7,7 +7,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.3.1 version: 0.3.2
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
+2 -1
View File
@@ -126,4 +126,5 @@ Create the mongodb connection string.
{{- if .Values.mongodbConnection.externalMongoDBConnectionString -}} {{- if .Values.mongodbConnection.externalMongoDBConnectionString -}}
{{- $connectionString = .Values.mongodbConnection.externalMongoDBConnectionString -}} {{- $connectionString = .Values.mongodbConnection.externalMongoDBConnectionString -}}
{{- end -}} {{- end -}}
{{- end -}} {{- printf "%s" $connectionString -}}
{{- end -}}