mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 08:27:36 +00:00
Address greptile comments
This commit is contained in:
@@ -863,13 +863,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
const cfg = getConfig();
|
|
||||||
if (!cfg.LICENSE_KEY_OFFLINE) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Offline usage reports are not enabled. LICENSE_KEY_OFFLINE must be configured."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await server.services.offlineUsageReport.generateUsageReportCSV();
|
const result = await server.services.offlineUsageReport.generateUsageReportCSV();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -104,17 +104,21 @@ export const offlineUsageReportDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
// Calculate average secrets per project
|
// Calculate average secrets per project
|
||||||
const secretsPerProject = (await db
|
const secretsPerProject = (await db
|
||||||
.from(TableName.SecretV2)
|
.from(`${TableName.SecretV2} as s`)
|
||||||
.select("folderId")
|
.select("p.id as projectId")
|
||||||
.count("* as count")
|
.count("s.id as count")
|
||||||
.groupBy("folderId")) as Array<{ folderId: string; count: string }>;
|
.leftJoin(`${TableName.SecretFolder} as sf`, "s.folderId", "sf.id")
|
||||||
|
.leftJoin(`${TableName.Environment} as e`, "sf.envId", "e.id")
|
||||||
|
.leftJoin(`${TableName.Project} as p`, "e.projectId", "p.id")
|
||||||
|
.groupBy("p.id")
|
||||||
|
.whereNotNull("p.id")) as Array<{ projectId: string; count: string }>;
|
||||||
|
|
||||||
const averageSecretsPerProject =
|
const averageSecretsPerProject =
|
||||||
totalProjects > 0
|
secretsPerProject.length > 0
|
||||||
? secretsPerProject.reduce(
|
? secretsPerProject.reduce(
|
||||||
(sum, row: { folderId: string; count: string }) => sum + parseInt(row.count, 10),
|
(sum, row: { projectId: string; count: string }) => sum + parseInt(row.count, 10),
|
||||||
0
|
0
|
||||||
) / totalProjects
|
) / secretsPerProject.length
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ export const offlineUsageReportServiceFactory = ({
|
|||||||
offlineUsageReportDAL.getSecretRotationMetrics()
|
offlineUsageReportDAL.getSecretRotationMetrics()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const csvLines: string[] = [];
|
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
"Total Users",
|
"Total Users",
|
||||||
"Admin Users",
|
"Admin Users",
|
||||||
@@ -89,8 +87,6 @@ export const offlineUsageReportServiceFactory = ({
|
|||||||
|
|
||||||
headers.push("Signature");
|
headers.push("Signature");
|
||||||
|
|
||||||
csvLines.push(headers.slice(0, -1).join(","));
|
|
||||||
|
|
||||||
const dataRow: (string | number)[] = [
|
const dataRow: (string | number)[] = [
|
||||||
userMetrics.totalUsers,
|
userMetrics.totalUsers,
|
||||||
userMetrics.adminUsers,
|
userMetrics.adminUsers,
|
||||||
@@ -114,14 +110,13 @@ export const offlineUsageReportServiceFactory = ({
|
|||||||
dataRow.push(projectMetrics.projectsByType[type] || 0);
|
dataRow.push(projectMetrics.projectsByType[type] || 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
const tempCsvLines = [headers.slice(0, -1).join(","), dataRow.join(",")];
|
const headersWithoutSignature = headers.slice(0, -1);
|
||||||
const contentWithoutSignature = tempCsvLines.join("\n");
|
const contentWithoutSignature = [headersWithoutSignature.join(","), dataRow.join(",")].join("\n");
|
||||||
|
|
||||||
const signature = signReportContent(contentWithoutSignature, licenseId);
|
const signature = signReportContent(contentWithoutSignature, licenseId);
|
||||||
dataRow.push(signature);
|
dataRow.push(signature);
|
||||||
|
|
||||||
const finalCsvLines = [headers.join(","), dataRow.join(",")];
|
const csvContent = [headers.join(","), dataRow.join(",")].join("\n");
|
||||||
const csvContent = finalCsvLines.join("\n");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
csvContent,
|
csvContent,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { adminQueryKeys, adminStandaloneKeys } from "./queries";
|
|||||||
import {
|
import {
|
||||||
RootKeyEncryptionStrategy,
|
RootKeyEncryptionStrategy,
|
||||||
TCreateAdminUserDTO,
|
TCreateAdminUserDTO,
|
||||||
TGenerateUsageReportDTO,
|
|
||||||
TInvalidateCacheDTO,
|
TInvalidateCacheDTO,
|
||||||
TServerConfig,
|
TServerConfig,
|
||||||
TUpdateServerConfigDTO,
|
TUpdateServerConfigDTO,
|
||||||
@@ -197,11 +196,10 @@ export const useInvalidateCache = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useGenerateUsageReport = () => {
|
export const useGenerateUsageReport = () => {
|
||||||
return useMutation<TUsageReportResponse, object, TGenerateUsageReportDTO>({
|
return useMutation<TUsageReportResponse, object, void>({
|
||||||
mutationFn: async (dto) => {
|
mutationFn: async () => {
|
||||||
const { data } = await apiRequest.post<TUsageReportResponse>(
|
const { data } = await apiRequest.post<TUsageReportResponse>(
|
||||||
"/api/v1/admin/usage-report/generate",
|
"/api/v1/admin/usage-report/generate"
|
||||||
dto
|
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,10 +144,6 @@ export interface TGetEnvOverrides {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TGenerateUsageReportDTO = {
|
|
||||||
periodDays?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TUsageReportResponse = {
|
export type TUsageReportResponse = {
|
||||||
filename: string;
|
filename: string;
|
||||||
csvContent: string;
|
csvContent: string;
|
||||||
|
|||||||
@@ -1,28 +1,14 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { faDownload, faFileAlt, faSpinner } from "@fortawesome/free-solid-svg-icons";
|
import { faDownload, faFileAlt, faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, Card, CardTitle } from "@app/components/v2";
|
import { Button, Card, CardTitle } from "@app/components/v2";
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
const formSchema = z.object({});
|
|
||||||
|
|
||||||
type TUsageReportForm = z.infer<typeof formSchema>;
|
|
||||||
|
|
||||||
export const UsageReportSection = () => {
|
export const UsageReportSection = () => {
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
|
|
||||||
const {
|
|
||||||
handleSubmit,
|
|
||||||
formState: { isSubmitting }
|
|
||||||
} = useForm<TUsageReportForm>({
|
|
||||||
resolver: zodResolver(formSchema)
|
|
||||||
});
|
|
||||||
|
|
||||||
const downloadFile = (content: string, filename: string, mimeType: string = "text/csv") => {
|
const downloadFile = (content: string, filename: string, mimeType: string = "text/csv") => {
|
||||||
const blob = new Blob([content], { type: mimeType });
|
const blob = new Blob([content], { type: mimeType });
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
@@ -35,7 +21,7 @@ export const UsageReportSection = () => {
|
|||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const handleGenerateReport = async () => {
|
||||||
try {
|
try {
|
||||||
setIsGenerating(true);
|
setIsGenerating(true);
|
||||||
|
|
||||||
@@ -59,8 +45,6 @@ export const UsageReportSection = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = isSubmitting || isGenerating;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<CardTitle className="mb-4 flex items-center gap-3">
|
<CardTitle className="mb-4 flex items-center gap-3">
|
||||||
@@ -75,15 +59,14 @@ export const UsageReportSection = () => {
|
|||||||
trail.
|
trail.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
<Button
|
||||||
<Button
|
onClick={handleGenerateReport}
|
||||||
type="submit"
|
className="w-fit"
|
||||||
isLoading={isLoading}
|
isLoading={isGenerating}
|
||||||
leftIcon={<FontAwesomeIcon icon={isLoading ? faSpinner : faDownload} />}
|
leftIcon={<FontAwesomeIcon icon={isGenerating ? faSpinner : faDownload} />}
|
||||||
>
|
>
|
||||||
{isLoading ? "Generating..." : "Generate Report"}
|
{isGenerating ? "Generating..." : "Generate Report"}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user