mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-14 12:16:20 +00:00
fix: mini hardening
This commit is contained in:
1 parent
33d93ba0ca
commit
b47bfa8a37
12 files changed
+353
-74
No files matched your search
@@ -17,12 +17,14 @@ import Trash from '../assets/red-trash.svg';
|
||||
import ThreeDots from '../assets/three-dots.svg';
|
||||
import UnPin from '../assets/unpin.svg';
|
||||
import { Avatar } from '../components/ui/avatar';
|
||||
import { Button } from '../components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '../components/ui/dropdown-menu';
|
||||
import { Modal } from '../components/ui/modal';
|
||||
import ConfirmationModal from '../modals/ConfirmationModal';
|
||||
import MoveToFolderModal from '../modals/MoveToFolderModal';
|
||||
import { ActiveState } from '../models/misc';
|
||||
@@ -67,6 +69,7 @@ export default function AgentCard({
|
||||
useState<ActiveState>('INACTIVE');
|
||||
const [moveModalState, setMoveModalState] = useState<ActiveState>('INACTIVE');
|
||||
const [shareModalOpen, setShareModalOpen] = useState(false);
|
||||
const [exportError, setExportError] = useState<string | null>(null);
|
||||
|
||||
const menuOptionsConfig: Record<string, AgentMenuOption[]> = {
|
||||
template: [
|
||||
@@ -302,7 +305,12 @@ export default function AgentCard({
|
||||
.json()
|
||||
.then((data) => data?.message)
|
||||
.catch(() => null);
|
||||
throw new Error(message || t('agents.exportAgentFailed'));
|
||||
// Server-side refusals (e.g. a workflow referencing too many
|
||||
// resources) carry an actionable message; flag it so the catch can
|
||||
// tell it apart from a transport error like "Failed to fetch".
|
||||
const error = new Error(message || t('agents.exportAgentFailed'));
|
||||
error.name = 'ExportRefused';
|
||||
throw error;
|
||||
}
|
||||
const yamlText = await response.text();
|
||||
const blob = new Blob([yamlText], { type: 'application/x-yaml' });
|
||||
@@ -316,8 +324,10 @@ export default function AgentCard({
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert(
|
||||
error instanceof Error ? error.message : t('agents.exportAgentFailed'),
|
||||
setExportError(
|
||||
error instanceof Error && error.name === 'ExportRefused'
|
||||
? error.message
|
||||
: t('agents.exportAgentFailed'),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -470,6 +480,25 @@ export default function AgentCard({
|
||||
cancelLabel="Cancel"
|
||||
variant="danger"
|
||||
/>
|
||||
<Modal
|
||||
open={exportError !== null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setExportError(null);
|
||||
}}
|
||||
title={t('agents.exportAgentFailed')}
|
||||
size="sm"
|
||||
footer={
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setExportError(null)}
|
||||
className="rounded-3xl px-5"
|
||||
>
|
||||
{t('agents.close')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<p className="text-muted-foreground text-sm">{exportError}</p>
|
||||
</Modal>
|
||||
<MoveToFolderModal
|
||||
modalState={moveModalState}
|
||||
setModalState={setMoveModalState}
|
||||
|
||||
@@ -1032,6 +1032,7 @@
|
||||
"willUpdate": "Updates an existing agent (matched by {{matchedBy}}).",
|
||||
"workflowCreate": "Includes a workflow with {{nodes}} nodes — it will be created for you.",
|
||||
"workflowUpdate": "Includes a workflow with {{nodes}} nodes — your existing workflow will be updated.",
|
||||
"workflowDelete": "Includes no workflow — the agent's existing workflow ({{nodes}} nodes), its run history and its artifacts will be permanently deleted.",
|
||||
"sources": "Sources",
|
||||
"tools": "Tools",
|
||||
"models": "Models",
|
||||
@@ -1152,6 +1153,7 @@
|
||||
"importAgent": "Import",
|
||||
"exportAgent": "Export as YAML",
|
||||
"exportAgentFailed": "Failed to export agent",
|
||||
"close": "Schließen",
|
||||
"shareWithTeam": "Mit Team teilen",
|
||||
"backToAll": "Zurück zu allen Agenten",
|
||||
"pageHeader": {
|
||||
|
||||
@@ -1037,6 +1037,7 @@
|
||||
"willUpdate": "Updates an existing agent (matched by {{matchedBy}}).",
|
||||
"workflowCreate": "Includes a workflow with {{nodes}} nodes — it will be created for you.",
|
||||
"workflowUpdate": "Includes a workflow with {{nodes}} nodes — your existing workflow will be updated.",
|
||||
"workflowDelete": "Includes no workflow — the agent's existing workflow ({{nodes}} nodes), its run history and its artifacts will be permanently deleted.",
|
||||
"sources": "Sources",
|
||||
"tools": "Tools",
|
||||
"models": "Models",
|
||||
@@ -1161,6 +1162,7 @@
|
||||
"importAgent": "Import",
|
||||
"exportAgent": "Export as YAML",
|
||||
"exportAgentFailed": "Failed to export agent",
|
||||
"close": "Close",
|
||||
"shareWithTeam": "Share with team",
|
||||
"backToAll": "Back to all agents",
|
||||
"pageHeader": {
|
||||
|
||||
@@ -1032,6 +1032,7 @@
|
||||
"willUpdate": "Updates an existing agent (matched by {{matchedBy}}).",
|
||||
"workflowCreate": "Includes a workflow with {{nodes}} nodes — it will be created for you.",
|
||||
"workflowUpdate": "Includes a workflow with {{nodes}} nodes — your existing workflow will be updated.",
|
||||
"workflowDelete": "Includes no workflow — the agent's existing workflow ({{nodes}} nodes), its run history and its artifacts will be permanently deleted.",
|
||||
"sources": "Sources",
|
||||
"tools": "Tools",
|
||||
"models": "Models",
|
||||
@@ -1152,6 +1153,7 @@
|
||||
"importAgent": "Import",
|
||||
"exportAgent": "Export as YAML",
|
||||
"exportAgentFailed": "Failed to export agent",
|
||||
"close": "Cerrar",
|
||||
"shareWithTeam": "Compartir con el equipo",
|
||||
"backToAll": "Volver a todos los agentes",
|
||||
"pageHeader": {
|
||||
|
||||
@@ -1032,6 +1032,7 @@
|
||||
"willUpdate": "Updates an existing agent (matched by {{matchedBy}}).",
|
||||
"workflowCreate": "Includes a workflow with {{nodes}} nodes — it will be created for you.",
|
||||
"workflowUpdate": "Includes a workflow with {{nodes}} nodes — your existing workflow will be updated.",
|
||||
"workflowDelete": "Includes no workflow — the agent's existing workflow ({{nodes}} nodes), its run history and its artifacts will be permanently deleted.",
|
||||
"sources": "Sources",
|
||||
"tools": "Tools",
|
||||
"models": "Models",
|
||||
@@ -1152,6 +1153,7 @@
|
||||
"importAgent": "Import",
|
||||
"exportAgent": "Export as YAML",
|
||||
"exportAgentFailed": "Failed to export agent",
|
||||
"close": "閉じる",
|
||||
"shareWithTeam": "チームと共有",
|
||||
"backToAll": "すべてのエージェントに戻る",
|
||||
"pageHeader": {
|
||||
|
||||
@@ -1032,6 +1032,7 @@
|
||||
"willUpdate": "Updates an existing agent (matched by {{matchedBy}}).",
|
||||
"workflowCreate": "Includes a workflow with {{nodes}} nodes — it will be created for you.",
|
||||
"workflowUpdate": "Includes a workflow with {{nodes}} nodes — your existing workflow will be updated.",
|
||||
"workflowDelete": "Includes no workflow — the agent's existing workflow ({{nodes}} nodes), its run history and its artifacts will be permanently deleted.",
|
||||
"sources": "Sources",
|
||||
"tools": "Tools",
|
||||
"models": "Models",
|
||||
@@ -1152,6 +1153,7 @@
|
||||
"importAgent": "Import",
|
||||
"exportAgent": "Export as YAML",
|
||||
"exportAgentFailed": "Failed to export agent",
|
||||
"close": "Закрыть",
|
||||
"shareWithTeam": "Поделиться с командой",
|
||||
"backToAll": "Вернуться ко всем агентам",
|
||||
"pageHeader": {
|
||||
|
||||
@@ -1032,6 +1032,7 @@
|
||||
"willUpdate": "Updates an existing agent (matched by {{matchedBy}}).",
|
||||
"workflowCreate": "Includes a workflow with {{nodes}} nodes — it will be created for you.",
|
||||
"workflowUpdate": "Includes a workflow with {{nodes}} nodes — your existing workflow will be updated.",
|
||||
"workflowDelete": "Includes no workflow — the agent's existing workflow ({{nodes}} nodes), its run history and its artifacts will be permanently deleted.",
|
||||
"sources": "Sources",
|
||||
"tools": "Tools",
|
||||
"models": "Models",
|
||||
@@ -1152,6 +1153,7 @@
|
||||
"importAgent": "Import",
|
||||
"exportAgent": "Export as YAML",
|
||||
"exportAgentFailed": "Failed to export agent",
|
||||
"close": "關閉",
|
||||
"shareWithTeam": "與團隊分享",
|
||||
"backToAll": "返回所有代理",
|
||||
"pageHeader": {
|
||||
|
||||
@@ -1032,6 +1032,7 @@
|
||||
"willUpdate": "Updates an existing agent (matched by {{matchedBy}}).",
|
||||
"workflowCreate": "Includes a workflow with {{nodes}} nodes — it will be created for you.",
|
||||
"workflowUpdate": "Includes a workflow with {{nodes}} nodes — your existing workflow will be updated.",
|
||||
"workflowDelete": "Includes no workflow — the agent's existing workflow ({{nodes}} nodes), its run history and its artifacts will be permanently deleted.",
|
||||
"sources": "Sources",
|
||||
"tools": "Tools",
|
||||
"models": "Models",
|
||||
@@ -1152,6 +1153,7 @@
|
||||
"importAgent": "Import",
|
||||
"exportAgent": "Export as YAML",
|
||||
"exportAgentFailed": "Failed to export agent",
|
||||
"close": "关闭",
|
||||
"shareWithTeam": "与团队共享",
|
||||
"backToAll": "返回所有代理",
|
||||
"pageHeader": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AlertTriangle, CheckCircle2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { type FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -58,7 +58,7 @@ type ImportPlan = {
|
||||
workflow?: {
|
||||
nodes: number;
|
||||
edges: number;
|
||||
action: 'create' | 'update';
|
||||
action: 'create' | 'update' | 'delete';
|
||||
} | null;
|
||||
};
|
||||
|
||||
@@ -124,19 +124,28 @@ export default function ImportAgentModal({
|
||||
setYamlText(await selectedFile.text());
|
||||
};
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop: (acceptedFiles: File[]) => {
|
||||
if (acceptedFiles[0]) processFile(acceptedFiles[0]);
|
||||
},
|
||||
multiple: false,
|
||||
// Declared here (not via getInputProps) so drag-and-drop is filtered
|
||||
// too, with drag-over rejection feedback; processFile's extension check
|
||||
// stays as a backstop for odd MIME reports.
|
||||
accept: {
|
||||
'application/x-yaml': ['.yaml', '.yml'],
|
||||
'text/yaml': ['.yaml', '.yml'],
|
||||
},
|
||||
});
|
||||
const { getRootProps, getInputProps, isDragActive, isDragReject } =
|
||||
useDropzone({
|
||||
onDrop: (acceptedFiles: File[], fileRejections: FileRejection[]) => {
|
||||
// A rejected file never reaches acceptedFiles, so without this the
|
||||
// drop is a silent no-op and any previously picked file stays staged.
|
||||
if (fileRejections.length > 0) {
|
||||
setFileName('');
|
||||
setYamlText('');
|
||||
setError(t('modals.importAgent.invalidFileType'));
|
||||
return;
|
||||
}
|
||||
if (acceptedFiles[0]) processFile(acceptedFiles[0]);
|
||||
},
|
||||
multiple: false,
|
||||
// Declared here (not via getInputProps) so drag-and-drop is filtered
|
||||
// too; processFile's extension check stays as a backstop for odd MIME
|
||||
// reports.
|
||||
accept: {
|
||||
'application/x-yaml': ['.yaml', '.yml'],
|
||||
'text/yaml': ['.yaml', '.yml'],
|
||||
},
|
||||
});
|
||||
|
||||
const handleAnalyze = async () => {
|
||||
if (!yamlText) return;
|
||||
@@ -320,7 +329,11 @@ export default function ImportAgentModal({
|
||||
<div
|
||||
{...getRootProps({
|
||||
className: `border-border hover:border-primary flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 transition-colors ${
|
||||
isDragActive ? 'border-primary' : ''
|
||||
isDragReject
|
||||
? 'border-destructive'
|
||||
: isDragActive
|
||||
? 'border-primary'
|
||||
: ''
|
||||
}`,
|
||||
})}
|
||||
>
|
||||
@@ -346,18 +359,28 @@ export default function ImportAgentModal({
|
||||
: t('modals.importAgent.willCreate')}
|
||||
</div>
|
||||
|
||||
{plan.workflow && (
|
||||
<p className="flex items-center gap-2 text-sm">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
{plan.workflow.action === 'update'
|
||||
? t('modals.importAgent.workflowUpdate', {
|
||||
nodes: plan.workflow.nodes,
|
||||
})
|
||||
: t('modals.importAgent.workflowCreate', {
|
||||
nodes: plan.workflow.nodes,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{plan.workflow &&
|
||||
(plan.workflow.action === 'delete' ? (
|
||||
// Irreversible: the graph, its run history and its artifacts
|
||||
// all go. Warn rather than confirming with a green check.
|
||||
<p className="text-destructive flex items-center gap-2 text-sm">
|
||||
<AlertTriangle className="h-4 w-4 shrink-0" />
|
||||
{t('modals.importAgent.workflowDelete', {
|
||||
nodes: plan.workflow.nodes,
|
||||
})}
|
||||
</p>
|
||||
) : (
|
||||
<p className="flex items-center gap-2 text-sm">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
{plan.workflow.action === 'update'
|
||||
? t('modals.importAgent.workflowUpdate', {
|
||||
nodes: plan.workflow.nodes,
|
||||
})
|
||||
: t('modals.importAgent.workflowCreate', {
|
||||
nodes: plan.workflow.nodes,
|
||||
})}
|
||||
</p>
|
||||
))}
|
||||
|
||||
{plan.sources.length > 0 && (
|
||||
<Section title={t('modals.importAgent.sources')}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { type FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
@@ -73,19 +73,28 @@ export default function ImportSpecModal({
|
||||
setParsedResult(null);
|
||||
};
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop: (acceptedFiles: File[]) => {
|
||||
if (acceptedFiles[0]) processFile(acceptedFiles[0]);
|
||||
},
|
||||
multiple: false,
|
||||
// Declared here (not via getInputProps) so drag-and-drop is filtered
|
||||
// too; processFile's extension check stays as a backstop.
|
||||
accept: {
|
||||
'application/json': ['.json'],
|
||||
'application/x-yaml': ['.yaml', '.yml'],
|
||||
'text/yaml': ['.yaml', '.yml'],
|
||||
},
|
||||
});
|
||||
const { getRootProps, getInputProps, isDragActive, isDragReject } =
|
||||
useDropzone({
|
||||
onDrop: (acceptedFiles: File[], fileRejections: FileRejection[]) => {
|
||||
// A rejected file never reaches acceptedFiles, so without this the
|
||||
// drop is a silent no-op and any previously picked file stays staged.
|
||||
if (fileRejections.length > 0) {
|
||||
setFile(null);
|
||||
setParsedResult(null);
|
||||
setError(t('modals.importSpec.invalidFileType'));
|
||||
return;
|
||||
}
|
||||
if (acceptedFiles[0]) processFile(acceptedFiles[0]);
|
||||
},
|
||||
multiple: false,
|
||||
// Declared here (not via getInputProps) so drag-and-drop is filtered
|
||||
// too; processFile's extension check stays as a backstop.
|
||||
accept: {
|
||||
'application/json': ['.json'],
|
||||
'application/x-yaml': ['.yaml', '.yml'],
|
||||
'text/yaml': ['.yaml', '.yml'],
|
||||
},
|
||||
});
|
||||
|
||||
const handleParse = async () => {
|
||||
if (!file) return;
|
||||
@@ -209,7 +218,11 @@ export default function ImportSpecModal({
|
||||
<div
|
||||
{...getRootProps({
|
||||
className: `border-border dark:border-border hover:border-primary dark:hover:border-primary flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 transition-colors ${
|
||||
isDragActive ? 'border-primary' : ''
|
||||
isDragReject
|
||||
? 'border-destructive'
|
||||
: isDragActive
|
||||
? 'border-primary'
|
||||
: ''
|
||||
}`,
|
||||
})}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user