mirror of
https://github.com/Infisical/infisical.git
synced 2025-09-07 10:22:29 +00:00
Compare commits
2 Commits
audit-log-
...
fix/cliExp
Author | SHA1 | Date | |
---|---|---|---|
|
350afee45e | ||
|
8187b1da91 |
@@ -9,7 +9,7 @@ infisical export [options]
|
||||
|
||||
## Description
|
||||
|
||||
Export environment variables from the platform into a file format.
|
||||
Export environment variables from the platform into a file format. By default, output is sent to stdout (standard output), but you can use the `--output-file` flag to save directly to a file.
|
||||
|
||||
## Subcommands & flags
|
||||
|
||||
@@ -21,18 +21,19 @@ $ infisical export
|
||||
|
||||
# Export variables to a .env file
|
||||
infisical export > .env
|
||||
infisical export --output-file=./.env
|
||||
|
||||
# Export variables to a .env file (with export keyword)
|
||||
infisical export --format=dotenv-export > .env
|
||||
|
||||
# Export variables to a CSV file
|
||||
infisical export --format=csv > secrets.csv
|
||||
infisical export --format=dotenv-export --output-file=./.env
|
||||
|
||||
# Export variables to a JSON file
|
||||
infisical export --format=json > secrets.json
|
||||
infisical export --format=json --output-file=./secrets.json
|
||||
|
||||
# Export variables to a YAML file
|
||||
infisical export --format=yaml > secrets.yaml
|
||||
infisical export --format=yaml --output-file=./secrets.yaml
|
||||
|
||||
# Render secrets using a custom template file
|
||||
infisical export --template=<path to template>
|
||||
@@ -73,6 +74,34 @@ infisical export --template=<path to template>
|
||||
|
||||
### flags
|
||||
|
||||
<Accordion title="--output-file">
|
||||
The path to write the output file to. Can be a full file path, directory, or filename.
|
||||
|
||||
```bash
|
||||
# Export to specific file
|
||||
infisical export --format=json --output-file=./secrets.json
|
||||
|
||||
# Export to directory (uses default filename based on format)
|
||||
infisical export --format=yaml --output-file=./
|
||||
```
|
||||
|
||||
**When `--output-file` is specified:**
|
||||
- Secrets are saved directly to the specified file
|
||||
- A success message is displayed showing the file path
|
||||
- For directories: adds default filename `secrets.{format}` (e.g., `secrets.json`, `secrets.yaml`)
|
||||
- For dotenv formats in directories: uses `.env` as the filename
|
||||
|
||||
**When `--output-file` is NOT specified (default behavior):**
|
||||
- Output is sent to stdout (standard output)
|
||||
- You can use shell redirection like `infisical export > secrets.json`
|
||||
- Maintains backwards compatibility with existing scripts
|
||||
|
||||
<Warning>
|
||||
If you're using shell redirection and your token expires, re-authentication will fail because the prompt can't display properly due to the redirection.
|
||||
</Warning>
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--template">
|
||||
The `--template` flag specifies the path to the template file used for rendering secrets. When using templates, you can omit the other format flags.
|
||||
|
||||
@@ -94,6 +123,7 @@ infisical export --template=<path to template>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--env">
|
||||
Used to set the environment that secrets are pulled from.
|
||||
|
||||
@@ -162,7 +192,7 @@ infisical export --template=<path to template>
|
||||
|
||||
```bash
|
||||
# Example
|
||||
infisical run --tags=tag1,tag2,tag3 -- npm run dev
|
||||
infisical export --tags=tag1,tag2,tag3 --env=dev
|
||||
```
|
||||
|
||||
Note: you must reference the tag by its slug name not its fully qualified name. Go to project settings to view all tag slugs.
|
||||
@@ -171,4 +201,4 @@ infisical export --template=<path to template>
|
||||
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
@@ -1,13 +1,11 @@
|
||||
import { ChangeEventHandler, useState } from "react";
|
||||
import { DayPicker, DayPickerProps, getDefaultClassNames, TZDate, UI } from "react-day-picker";
|
||||
import { DayPicker, DayPickerProps, getDefaultClassNames, UI } from "react-day-picker";
|
||||
import { faCalendar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { PopoverContentProps, PopoverProps } from "@radix-ui/react-popover";
|
||||
import { format, setHours, setMinutes } from "date-fns";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { formatDateTime, Timezone } from "@app/helpers/datetime";
|
||||
|
||||
import { Button } from "../Button";
|
||||
import { Input } from "../Input";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../Popoverv2";
|
||||
@@ -20,32 +18,6 @@ export type DatePickerProps = Omit<DayPickerProps, "selected"> & {
|
||||
popUpProps: PopoverProps;
|
||||
popUpContentProps: PopoverContentProps;
|
||||
dateFormat?: "PPP" | "PP" | "P"; // extend as needed
|
||||
buttonClassName?: string;
|
||||
timezone?: Timezone;
|
||||
};
|
||||
|
||||
const localTimeToUTC = (timeString: string) => {
|
||||
const today = new Date();
|
||||
const [hours, minutes] = timeString.split(":").map(Number);
|
||||
|
||||
today.setHours(hours, minutes, 0, 0);
|
||||
|
||||
const utcHours = today.getUTCHours().toString().padStart(2, "0");
|
||||
const utcMinutes = today.getUTCMinutes().toString().padStart(2, "0");
|
||||
|
||||
return `${utcHours}:${utcMinutes}`;
|
||||
};
|
||||
|
||||
const utcTimeToLocal = (utcTimeString: string) => {
|
||||
const today = new Date();
|
||||
const [hours, minutes] = utcTimeString.split(":").map(Number);
|
||||
|
||||
today.setUTCHours(hours, minutes, 0, 0);
|
||||
|
||||
const localHours = today.getHours().toString().padStart(2, "0");
|
||||
const localMinutes = today.getMinutes().toString().padStart(2, "0");
|
||||
|
||||
return `${localHours}:${localMinutes}`;
|
||||
};
|
||||
|
||||
// Doc: https://react-day-picker.js.org/
|
||||
@@ -55,16 +27,12 @@ export const DatePicker = ({
|
||||
popUpProps,
|
||||
popUpContentProps,
|
||||
dateFormat = "PPP",
|
||||
buttonClassName,
|
||||
timezone,
|
||||
...props
|
||||
}: DatePickerProps) => {
|
||||
const [timeValue, setTimeValue] = useState<string>(value ? format(value, "HH:mm") : "00:00");
|
||||
const displayUtc = timezone === Timezone.UTC;
|
||||
|
||||
const handleTimeChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
const time = displayUtc ? utcTimeToLocal(e.target.value) : e.target.value;
|
||||
|
||||
const time = e.target.value;
|
||||
if (time) {
|
||||
setTimeValue(time);
|
||||
if (value) {
|
||||
@@ -89,14 +57,8 @@ export const DatePicker = ({
|
||||
return (
|
||||
<Popover {...popUpProps}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
className={buttonClassName}
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faCalendar} />}
|
||||
>
|
||||
{value
|
||||
? formatDateTime({ timestamp: value, timezone, dateFormat })
|
||||
: "Pick a date and time"}
|
||||
<Button variant="outline_bg" leftIcon={<FontAwesomeIcon icon={faCalendar} />}>
|
||||
{value ? format(value, dateFormat) : "Pick a date and time"}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
@@ -110,10 +72,9 @@ export const DatePicker = ({
|
||||
<DayPicker
|
||||
{...props}
|
||||
mode="single"
|
||||
selected={value ? new TZDate(value, displayUtc ? "UTC" : undefined) : undefined}
|
||||
onSelect={(date) => handleDaySelect(date ? new TZDate(date, undefined) : undefined)}
|
||||
selected={value}
|
||||
onSelect={handleDaySelect}
|
||||
className="font-inter text-mineshaft-200"
|
||||
timeZone={displayUtc ? "UTC" : undefined}
|
||||
classNames={{
|
||||
today: "text-primary border-primary",
|
||||
selected: " text-mineshaft-100 bg-mineshaft-500",
|
||||
@@ -127,7 +88,7 @@ export const DatePicker = ({
|
||||
<div className="mx-4 my-4">
|
||||
<Input
|
||||
type="time"
|
||||
value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
|
||||
value={timeValue}
|
||||
onChange={handleTimeChange}
|
||||
className="bg-mineshaft-700 text-white [color-scheme:dark]"
|
||||
/>
|
||||
|
@@ -19,7 +19,6 @@ type Props = {
|
||||
icon?: IconProp;
|
||||
isMulti?: boolean;
|
||||
iconClassName?: string;
|
||||
dropdownContainerStyle?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export type SelectProps = Omit<SelectPrimitive.SelectProps, "disabled"> & Props;
|
||||
@@ -36,7 +35,6 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
position,
|
||||
containerClassName,
|
||||
iconClassName,
|
||||
dropdownContainerStyle,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
@@ -84,7 +82,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
dropdownContainerClassName
|
||||
)}
|
||||
position={position}
|
||||
style={dropdownContainerStyle ?? { width: "var(--radix-select-trigger-width)" }}
|
||||
style={{ width: "var(--radix-select-trigger-width)" }}
|
||||
>
|
||||
<SelectPrimitive.ScrollUpButton>
|
||||
<div className="flex items-center justify-center">
|
||||
|
@@ -1,24 +0,0 @@
|
||||
import { format } from "date-fns";
|
||||
|
||||
export enum Timezone {
|
||||
Local = "local",
|
||||
UTC = "UTC"
|
||||
}
|
||||
|
||||
export const formatDateTime = ({
|
||||
timezone,
|
||||
timestamp,
|
||||
dateFormat = "MMM do yyyy, hh:mm a"
|
||||
}: {
|
||||
timestamp: string | Date;
|
||||
timezone?: Timezone;
|
||||
dateFormat?: string;
|
||||
}) => {
|
||||
const date = new Date(timestamp);
|
||||
|
||||
if (timezone === Timezone.UTC) {
|
||||
const utcDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000);
|
||||
return `${format(utcDate, dateFormat)}`;
|
||||
}
|
||||
return format(date, dateFormat);
|
||||
};
|
@@ -57,7 +57,6 @@ export const useGetAuditLogs = (
|
||||
},
|
||||
getNextPageParam: (lastPage, pages) =>
|
||||
lastPage.length !== 0 ? pages.length * filters.limit : undefined,
|
||||
placeholderData: (prev) => prev,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
@@ -18,7 +18,7 @@ export const AuditLogsPage = () => {
|
||||
title="Audit logs"
|
||||
description="Audit logs for security and compliance teams to monitor information access."
|
||||
/>
|
||||
<LogsSection pageView />
|
||||
<LogsSection />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faCalendar, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faArrowRight, faCalendar, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { format } from "date-fns";
|
||||
import ms from "ms";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { formatDateTime, Timezone } from "@app/helpers/datetime";
|
||||
|
||||
import {
|
||||
auditLogDateFilterFormSchema,
|
||||
@@ -28,19 +28,9 @@ import {
|
||||
type Props = {
|
||||
setFilter: (data: TAuditLogDateFilterFormData) => void;
|
||||
filter: TAuditLogDateFilterFormData;
|
||||
setTimezone: (timezone: Timezone) => void;
|
||||
timezone: Timezone;
|
||||
};
|
||||
const RELATIVE_VALUES = ["5m", "30m", "1h", "3h", "12h"];
|
||||
|
||||
const RELATIVE_OPTIONS = [
|
||||
{ label: "Minutes", unit: "m", values: [5, 10, 15, 30, 45] },
|
||||
{ label: "Hours", unit: "h", values: [1, 2, 3, 6, 8, 12] },
|
||||
{ label: "Days", unit: "d", values: [1, 2, 3, 4, 5, 6] },
|
||||
{ label: "Weeks", unit: "w", values: [1, 2, 3, 4] }
|
||||
];
|
||||
|
||||
export const LogsDateFilter = ({ setFilter, filter, timezone, setTimezone }: Props) => {
|
||||
export const LogsDateFilter = ({ setFilter, filter }: Props) => {
|
||||
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
||||
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
|
||||
const [isPopupOpen, setIsPopOpen] = useState(false);
|
||||
@@ -69,258 +59,154 @@ export const LogsDateFilter = ({ setFilter, filter, timezone, setTimezone }: Pro
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu open={isPopupOpen} onOpenChange={(el) => setIsPopOpen(el)}>
|
||||
<div className="flex items-center">
|
||||
{filter.type === AuditLogDateFilterType.Relative ? (
|
||||
<>
|
||||
{RELATIVE_VALUES.map((el) => (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
className={twMerge(
|
||||
"w-[3.82rem] rounded-none px-3 py-2 font-normal first:rounded-l-md",
|
||||
filter.type === AuditLogDateFilterType.Relative &&
|
||||
filter.relativeModeValue === el &&
|
||||
"border-primary/40 bg-primary/[0.1]"
|
||||
)}
|
||||
key={`${el}-relative`}
|
||||
onClick={() =>
|
||||
setFilter({
|
||||
relativeModeValue: el,
|
||||
type: AuditLogDateFilterType.Relative,
|
||||
endDate: new Date(),
|
||||
startDate: new Date(Number(new Date()) - ms(el))
|
||||
})
|
||||
}
|
||||
>
|
||||
{el}
|
||||
</Button>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex w-[19.1rem] items-center justify-between rounded-l-md border border-transparent bg-mineshaft-600 px-5 py-2 text-sm text-bunker-200">
|
||||
<div>
|
||||
{formatDateTime({
|
||||
timezone,
|
||||
timestamp: filter.startDate,
|
||||
dateFormat: "yyyy/MM/dd HH:mm"
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<FontAwesomeIcon className="text-bunker-300" size="sm" icon={faChevronRight} />
|
||||
</div>
|
||||
<div>
|
||||
{formatDateTime({
|
||||
timezone,
|
||||
timestamp: filter.endDate,
|
||||
dateFormat: "yyyy/MM/dd HH:mm"
|
||||
})}
|
||||
</div>
|
||||
<DropdownMenu open={isPopupOpen} onOpenChange={(el) => setIsPopOpen(el)}>
|
||||
<div className="mr-2 flex items-center">
|
||||
{filter.type === AuditLogDateFilterType.Relative ? (
|
||||
<>
|
||||
{RELATIVE_VALUES.map((el) => (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
className={twMerge(
|
||||
"rounded-none px-3 py-2 first:rounded-l-md",
|
||||
filter.type === AuditLogDateFilterType.Relative &&
|
||||
filter.relativeModeValue === el &&
|
||||
"border-primary/40 bg-primary/[0.1]"
|
||||
)}
|
||||
key={`${el}-relative`}
|
||||
onClick={() =>
|
||||
setFilter({
|
||||
relativeModeValue: el,
|
||||
type: AuditLogDateFilterType.Relative,
|
||||
endDate: new Date(),
|
||||
startDate: new Date(Number(new Date()) - ms(el))
|
||||
})
|
||||
}
|
||||
>
|
||||
{el}
|
||||
</Button>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded-l-md border border-transparent bg-mineshaft-600 px-3 py-2 text-sm text-bunker-200">
|
||||
{format(filter.startDate, "yyyy-MM-dd HH:mm")}
|
||||
</div>
|
||||
)}
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
className={twMerge(
|
||||
"w-[8rem] rounded-none rounded-r-md px-3 py-2 font-normal",
|
||||
(filter.type === AuditLogDateFilterType.Absolute || isCustomRelative) &&
|
||||
"border-primary/40 bg-primary/[0.1]"
|
||||
)}
|
||||
>
|
||||
<span>Custom</span> <FontAwesomeIcon className="ml-1" icon={faCalendar} />
|
||||
{filter.type === AuditLogDateFilterType.Relative && isCustomRelative && (
|
||||
<span className="ml-1">({filter.relativeModeValue})</span>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</div>
|
||||
<DropdownMenuContent
|
||||
className="!min-w-[434px] bg-mineshaft-800 p-4"
|
||||
align="end"
|
||||
sideOffset={8}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="border border-transparent bg-mineshaft-600 px-3 py-2 text-sm text-bunker-200">
|
||||
<FontAwesomeIcon icon={faChevronRight} />
|
||||
</div>
|
||||
<div className="border border-transparent bg-mineshaft-600 px-3 py-2 text-sm text-bunker-200">
|
||||
{format(filter.endDate, "yyyy-MM-dd HH:mm")}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
className={twMerge(
|
||||
"rounded-none rounded-r-md px-3 py-2",
|
||||
(filter.type === AuditLogDateFilterType.Absolute || isCustomRelative) &&
|
||||
"border-primary/40 bg-primary/[0.1]"
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCalendar} />
|
||||
{filter.type === AuditLogDateFilterType.Relative && isCustomRelative && (
|
||||
<span className="ml-1">({filter.relativeModeValue})</span>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</div>
|
||||
<DropdownMenuContent className="min-w-80 p-4" align="center" alignOffset={16}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormControl className="mb-2 w-full">
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(el) => field.onChange(el)}
|
||||
className="w-full"
|
||||
>
|
||||
<SelectItem value={AuditLogDateFilterType.Relative}>Relative</SelectItem>
|
||||
<SelectItem value={AuditLogDateFilterType.Absolute}>Absolute</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{selectType === AuditLogDateFilterType.Relative && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="type"
|
||||
name="relativeModeValue"
|
||||
render={({ field }) => (
|
||||
<div className="mb-7">
|
||||
<Button
|
||||
onClick={() => field.onChange(AuditLogDateFilterType.Absolute)}
|
||||
variant="outline_bg"
|
||||
className={twMerge(
|
||||
"h-8 rounded-r-none font-normal",
|
||||
field.value === AuditLogDateFilterType.Absolute &&
|
||||
"border-primary/40 bg-primary/[0.1]"
|
||||
)}
|
||||
>
|
||||
Absolute
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => field.onChange(AuditLogDateFilterType.Relative)}
|
||||
variant="outline_bg"
|
||||
className={twMerge(
|
||||
"h-8 rounded-l-none font-normal",
|
||||
field.value === AuditLogDateFilterType.Relative &&
|
||||
"border-primary/40 bg-primary/[0.1]"
|
||||
)}
|
||||
>
|
||||
Relative
|
||||
</Button>
|
||||
</div>
|
||||
<FormControl className="mb-0 w-full" helperText="Example: 1h, 1d, 2d">
|
||||
<Input {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{selectType === AuditLogDateFilterType.Relative && (
|
||||
)}
|
||||
{selectType === AuditLogDateFilterType.Absolute && (
|
||||
<div className="mb-2 flex h-10 w-full items-center justify-between gap-2">
|
||||
<Controller
|
||||
name="startDate"
|
||||
control={control}
|
||||
name="relativeModeValue"
|
||||
render={({ field }) => {
|
||||
const duration = field.value?.substring(0, field.value.length - 1);
|
||||
const unitOfTime = field.value?.at(-1);
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{RELATIVE_OPTIONS.map(({ label, unit, values }) => (
|
||||
<div key={unit} className="flex items-center gap-2">
|
||||
<div className="w-16">{label}</div>
|
||||
{values.map((v) => {
|
||||
const value = `${v}${unit}`;
|
||||
return (
|
||||
<Button
|
||||
key={value}
|
||||
variant="outline_bg"
|
||||
onClick={() => field.onChange(value)}
|
||||
className={twMerge(
|
||||
"h-8 w-12",
|
||||
field.value === value && "border-primary/40 bg-primary/[0.1]"
|
||||
)}
|
||||
>
|
||||
{v}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-2">
|
||||
<FormControl className="mb-0 w-28" label="Duration">
|
||||
<Input
|
||||
type="number"
|
||||
value={duration || "1"}
|
||||
onChange={(val) => field.onChange(`${val}${unitOfTime}`)}
|
||||
max={60}
|
||||
min={1}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl className="mb-0 w-36" label="Unit of Time">
|
||||
<Select
|
||||
value={unitOfTime}
|
||||
onValueChange={(val) => field.onChange(`${duration}${val}`)}
|
||||
className="w-full"
|
||||
position="popper"
|
||||
>
|
||||
{RELATIVE_OPTIONS.map((opt) => (
|
||||
<SelectItem value={opt.unit}>{opt.label}</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<FormControl
|
||||
className="relative top-2"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<DatePicker
|
||||
value={field.value || undefined}
|
||||
onChange={onChange}
|
||||
dateFormat="P"
|
||||
popUpProps={{
|
||||
open: isStartDatePickerOpen,
|
||||
onOpenChange: setIsStartDatePickerOpen
|
||||
}}
|
||||
popUpContentProps={{}}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{selectType === AuditLogDateFilterType.Absolute && (
|
||||
<div className="flex h-10 w-full items-center justify-between gap-2">
|
||||
<Controller
|
||||
name="startDate"
|
||||
control={control}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className="relative top-2"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
label="Start Date"
|
||||
>
|
||||
<DatePicker
|
||||
value={field.value || undefined}
|
||||
onChange={onChange}
|
||||
timezone={timezone}
|
||||
dateFormat="P"
|
||||
buttonClassName="w-44 h-8 font-normal"
|
||||
popUpProps={{
|
||||
open: isStartDatePickerOpen,
|
||||
onOpenChange: setIsStartDatePickerOpen
|
||||
}}
|
||||
popUpContentProps={{}}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronRight}
|
||||
size="xs"
|
||||
className="mt-6 text-mineshaft-400"
|
||||
/>
|
||||
<Controller
|
||||
name="endDate"
|
||||
control={control}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className="relative top-2"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
label="End Date"
|
||||
>
|
||||
<DatePicker
|
||||
value={field.value || undefined}
|
||||
onChange={onChange}
|
||||
dateFormat="P"
|
||||
buttonClassName="w-44 h-8 font-normal"
|
||||
timezone={timezone}
|
||||
popUpProps={{
|
||||
open: isEndDatePickerOpen,
|
||||
onOpenChange: setIsEndDatePickerOpen
|
||||
}}
|
||||
popUpContentProps={{}}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center -space-x-3">
|
||||
<div className="h-[2px] w-[20px] rounded-full bg-mineshaft-500" />
|
||||
<FontAwesomeIcon icon={faArrowRight} className="text-mineshaft-500" />
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-8 w-full justify-end">
|
||||
<Button
|
||||
size="sm"
|
||||
type="submit"
|
||||
className="h-9 w-24 font-normal"
|
||||
variant="outline_bg"
|
||||
isDisabled={!formState.isDirty}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
<Controller
|
||||
name="endDate"
|
||||
control={control}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className="relative top-2"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<DatePicker
|
||||
value={field.value || undefined}
|
||||
onChange={onChange}
|
||||
dateFormat="P"
|
||||
popUpProps={{
|
||||
open: isEndDatePickerOpen,
|
||||
onOpenChange: setIsEndDatePickerOpen
|
||||
}}
|
||||
popUpContentProps={{}}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Select
|
||||
value={timezone}
|
||||
onValueChange={(val) => setTimezone(val as Timezone)}
|
||||
className="w-[10.6rem] border !border-mineshaft-500 !bg-mineshaft-600 capitalize"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
position="popper"
|
||||
dropdownContainerStyle={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
{Object.values(Timezone).map((tz) => (
|
||||
<SelectItem value={tz} className="capitalize" key={tz}>
|
||||
{tz} Timezone
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-4">
|
||||
<Button size="xs" type="submit" isDisabled={!formState.isDirty}>
|
||||
Apply
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import ms from "ms";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { Timezone } from "@app/helpers/datetime";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -23,11 +20,10 @@ type Props = {
|
||||
presets?: Presets;
|
||||
refetchInterval?: number;
|
||||
showFilters?: boolean;
|
||||
pageView?: boolean;
|
||||
};
|
||||
|
||||
export const LogsSection = withPermission(
|
||||
({ presets, refetchInterval, showFilters = true, pageView = false }: Props) => {
|
||||
({ presets, refetchInterval, showFilters = true }: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
@@ -35,8 +31,6 @@ export const LogsSection = withPermission(
|
||||
eventType: presets?.eventType || [],
|
||||
actor: presets?.actorId
|
||||
});
|
||||
const [timezone, setTimezone] = useState<Timezone>(Timezone.Local);
|
||||
|
||||
const [dateFilter, setDateFilter] = useState<TAuditLogDateFilterFormData>({
|
||||
startDate: new Date(Number(new Date()) - ms("1h")),
|
||||
endDate: new Date(),
|
||||
@@ -49,85 +43,10 @@ export const LogsSection = withPermission(
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}, [subscription]);
|
||||
|
||||
if (pageView)
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex flex-wrap items-center justify-between gap-y-2">
|
||||
<div>
|
||||
<div className="flex items-center gap-1 whitespace-nowrap">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Audit History</p>
|
||||
<a
|
||||
href="https://infisical.com/docs/documentation/platform/audit-logs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="ml-1 mt-[0.1rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
<span>Docs</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.07rem] ml-1.5 text-[10px]"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 lg:justify-end">
|
||||
{showFilters && (
|
||||
<LogsDateFilter
|
||||
filter={dateFilter}
|
||||
setFilter={setDateFilter}
|
||||
timezone={timezone}
|
||||
setTimezone={setTimezone}
|
||||
/>
|
||||
)}
|
||||
{showFilters && (
|
||||
<LogsFilter presets={presets} setFilter={setLogFilter} filter={logFilter} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<LogsTable
|
||||
refetchInterval={refetchInterval}
|
||||
filter={{
|
||||
secretPath: logFilter.secretPath || undefined,
|
||||
secretKey: logFilter.secretKey || undefined,
|
||||
eventMetadata: logFilter?.eventMetadata,
|
||||
projectId: logFilter?.project?.id,
|
||||
actorType: presets?.actorType,
|
||||
limit: 15,
|
||||
eventType: logFilter?.eventType,
|
||||
userAgentType: logFilter?.userAgentType,
|
||||
startDate: dateFilter?.startDate,
|
||||
endDate: dateFilter?.endDate,
|
||||
environment: logFilter?.environment?.slug,
|
||||
actor: logFilter?.actor
|
||||
}}
|
||||
timezone={timezone}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("upgradePlan", isOpen);
|
||||
}}
|
||||
text="You can use audit logs if you switch to a paid Infisical plan."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex w-full justify-end">
|
||||
{showFilters && (
|
||||
<LogsDateFilter
|
||||
filter={dateFilter}
|
||||
setFilter={setDateFilter}
|
||||
timezone={timezone}
|
||||
setTimezone={setTimezone}
|
||||
/>
|
||||
)}
|
||||
{showFilters && <LogsDateFilter filter={dateFilter} setFilter={setDateFilter} />}
|
||||
{showFilters && (
|
||||
<LogsFilter presets={presets} setFilter={setLogFilter} filter={logFilter} />
|
||||
)}
|
||||
@@ -148,7 +67,6 @@ export const LogsSection = withPermission(
|
||||
environment: logFilter?.environment?.slug,
|
||||
actor: logFilter?.actor
|
||||
}}
|
||||
timezone={timezone}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Fragment } from "react";
|
||||
import { faFile } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faFile, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
@@ -13,9 +14,9 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { Timezone } from "@app/helpers/datetime";
|
||||
import { useGetAuditLogs } from "@app/hooks/api";
|
||||
import { TGetAuditLogsFilter } from "@app/hooks/api/auditLogs/types";
|
||||
|
||||
@@ -24,12 +25,11 @@ import { LogsTableRow } from "./LogsTableRow";
|
||||
type Props = {
|
||||
filter: TGetAuditLogsFilter;
|
||||
refetchInterval?: number;
|
||||
timezone: Timezone;
|
||||
};
|
||||
|
||||
const AUDIT_LOG_LIMIT = 30;
|
||||
|
||||
export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
|
||||
export const LogsTable = ({ filter, refetchInterval }: Props) => {
|
||||
// Determine the project ID for filtering
|
||||
const filterProjectId =
|
||||
// Use the projectId from the filter if it exists
|
||||
@@ -57,7 +57,16 @@ export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
|
||||
<Th className="w-24">
|
||||
<Spinner size="xs" className={twMerge(isPending ? "opacity-100" : "opacity-0")} />
|
||||
</Th>
|
||||
<Th className="w-64">Timestamp</Th>
|
||||
<Th className="w-64">
|
||||
Timestamp
|
||||
<Tooltip
|
||||
className="normal-case"
|
||||
content="Time displayed in your system's time zone."
|
||||
sideOffset={10}
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="ml-1" />
|
||||
</Tooltip>
|
||||
</Th>
|
||||
<Th>Event</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
@@ -70,7 +79,6 @@ export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
|
||||
rowNumber={index + i * AUDIT_LOG_LIMIT + 1}
|
||||
auditLog={auditLog}
|
||||
key={`audit-log-${auditLog.id}`}
|
||||
timezone={timezone}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
@@ -88,7 +96,7 @@ export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
|
||||
</TableContainer>
|
||||
{!isEmpty && (
|
||||
<Button
|
||||
className="mt-4 px-4 py-3 text-sm"
|
||||
className="mb-20 mt-4 px-4 py-3 text-sm"
|
||||
isFullWidth
|
||||
variant="outline_bg"
|
||||
isLoading={isFetchingNextPage}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { Td, Tr } from "@app/components/v2";
|
||||
import { formatDateTime, Timezone } from "@app/helpers/datetime";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { AuditLog } from "@app/hooks/api/auditLogs/types";
|
||||
@@ -10,7 +10,6 @@ import { AuditLog } from "@app/hooks/api/auditLogs/types";
|
||||
type Props = {
|
||||
auditLog: AuditLog;
|
||||
rowNumber: number;
|
||||
timezone: Timezone;
|
||||
};
|
||||
|
||||
type TagProps = {
|
||||
@@ -28,7 +27,7 @@ const Tag = ({ label, value }: TagProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const LogsTableRow = ({ auditLog, rowNumber, timezone }: Props) => {
|
||||
export const LogsTableRow = ({ auditLog, rowNumber }: Props) => {
|
||||
const [isOpen, setIsOpen] = useToggle();
|
||||
|
||||
return (
|
||||
@@ -47,7 +46,9 @@ export const LogsTableRow = ({ auditLog, rowNumber, timezone }: Props) => {
|
||||
<FontAwesomeIcon icon={isOpen ? faCaretDown : faCaretRight} />
|
||||
{rowNumber}
|
||||
</Td>
|
||||
<Td className="align-top">{formatDateTime({ timestamp: auditLog.createdAt, timezone })}</Td>
|
||||
<Td className="align-top">
|
||||
{format(new Date(auditLog.createdAt), "MMM do yyyy, hh:mm a")}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-wrap gap-4 text-sm">
|
||||
<Tag label="event" value={auditLog.event.type} />
|
||||
|
Reference in New Issue
Block a user