firewall + scripts working
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
DeleteOutlined,
|
||||
DownloadOutlined,
|
||||
EditOutlined,
|
||||
FileAddOutlined,
|
||||
PauseCircleOutlined,
|
||||
PlayCircleOutlined,
|
||||
PlusOutlined,
|
||||
@@ -60,6 +61,7 @@ const { Paragraph } = Typography;
|
||||
* - Table action buttons are icon-only with Tooltip
|
||||
* - Expands nested rows by default
|
||||
* - Focus helper attempts to put caret at end inside editors when modals open
|
||||
* - New: Add requirements UI for scripts that have no requirements
|
||||
*/
|
||||
|
||||
export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (code: string) => void }) {
|
||||
@@ -88,6 +90,13 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
|
||||
const [reqEditorValue, setReqEditorValue] = useState<string>('');
|
||||
const [reqEditingName, setReqEditingName] = useState<string | null>(null);
|
||||
|
||||
// add-requirements modal (for scripts that have no requirements)
|
||||
const [addReqModalVisible, setAddReqModalVisible] = useState(false);
|
||||
const [addReqTarget, setAddReqTarget] = useState<string | null>(null);
|
||||
const [addReqUseInline, setAddReqUseInline] = useState(false);
|
||||
const [addReqInlineValue, setAddReqInlineValue] = useState('');
|
||||
const [addReqFile, setAddReqFile] = useState<File | null>(null);
|
||||
|
||||
// enable modal
|
||||
const [enableModalVisible, setEnableModalVisible] = useState(false);
|
||||
const [enableTarget, setEnableTarget] = useState<string | null>(null);
|
||||
@@ -105,7 +114,31 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
|
||||
const all = await fetchScriptsAll();
|
||||
setScripts(all || []);
|
||||
} catch (err: any) {
|
||||
notification.error({ message: 'Failed to fetch scripts', description: err?.message ?? String(err) });
|
||||
notification.error({
|
||||
message: 'Failed to fetch scripts',
|
||||
description: err?.message ?? String(err),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleManualRefresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const all = await fetchScriptsAll();
|
||||
setScripts(all || []);
|
||||
|
||||
const count = (all || []).length;
|
||||
notification.success({
|
||||
message: 'Refresh complete',
|
||||
description: `Loaded ${count} script${count === 1 ? '' : 's'}.`,
|
||||
});
|
||||
} catch (err: any) {
|
||||
notification.error({
|
||||
message: 'Failed to fetch scripts',
|
||||
description: err?.message ?? String(err),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -168,6 +201,10 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
|
||||
if (reqModalVisible) focusEditorInModal('.ant-modal');
|
||||
}, [reqModalVisible, focusEditorInModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (addReqModalVisible) focusEditorInModal('.ant-modal');
|
||||
}, [addReqModalVisible, focusEditorInModal]);
|
||||
|
||||
// show pip modal if pip output is present
|
||||
const showPipIfPresent = useCallback(
|
||||
(resp?: ScriptUploadResponse | { pip?: { stdout?: string; stderr?: string } } | null) => {
|
||||
@@ -472,6 +509,57 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Add Requirements (for scripts missing requirements)
|
||||
// --------------------
|
||||
const openAddRequirements = useCallback((scriptName: string) => {
|
||||
setAddReqTarget(scriptName);
|
||||
setAddReqUseInline(false);
|
||||
setAddReqInlineValue('');
|
||||
setAddReqFile(null);
|
||||
setAddReqModalVisible(true);
|
||||
}, []);
|
||||
|
||||
const saveAddRequirements = useCallback(async () => {
|
||||
if (!addReqTarget) return;
|
||||
let payload: File | Blob | null = null;
|
||||
if (addReqFile) {
|
||||
payload = addReqFile;
|
||||
} else if (addReqUseInline && addReqInlineValue.trim() !== '') {
|
||||
payload = new Blob([addReqInlineValue], { type: 'text/plain' });
|
||||
} else {
|
||||
notification.warning({
|
||||
message: 'No requirements provided',
|
||||
description: 'Upload a file or enter inline content.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await uploadRequirements(addReqTarget, payload);
|
||||
notification.success({ message: 'requirements uploaded', description: `${addReqTarget} updated` });
|
||||
showPipIfPresent(resp);
|
||||
setAddReqModalVisible(false);
|
||||
setAddReqTarget(null);
|
||||
setAddReqInlineValue('');
|
||||
setAddReqFile(null);
|
||||
await refreshAll();
|
||||
} catch (err: any) {
|
||||
notification.error({ message: 'Failed to upload requirements', description: err?.message ?? String(err) });
|
||||
}
|
||||
}, [addReqFile, addReqInlineValue, addReqTarget, refreshAll, showPipIfPresent]);
|
||||
|
||||
// helper for Upload change inside add-req modal
|
||||
const onAddReqUploadChange = useCallback((info: any) => {
|
||||
const list: UploadFile[] = info.fileList || [];
|
||||
if (list.length === 0) {
|
||||
setAddReqFile(null);
|
||||
return;
|
||||
}
|
||||
const last = list[list.length - 1] as any;
|
||||
setAddReqFile(last.originFileObj ?? last);
|
||||
}, []);
|
||||
|
||||
// --------------------
|
||||
// Table columns
|
||||
// --------------------
|
||||
@@ -513,7 +601,13 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
|
||||
/>
|
||||
</Space>
|
||||
) : (
|
||||
<span style={{ color: '#999' }}>—</span>
|
||||
<Space>
|
||||
<IconButtonTooltip
|
||||
title="Add requirements"
|
||||
onClick={() => openAddRequirements(record.name)}
|
||||
icon={<FileAddOutlined />}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -627,15 +721,13 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
|
||||
return (
|
||||
<div>
|
||||
<Row justify="space-between" style={{ marginBottom: 12 }}>
|
||||
<Col>
|
||||
<Space>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setUploadModalVisible(true)}>
|
||||
Upload / New
|
||||
</Button>
|
||||
<Button onClick={refreshAll} icon={loading ? <Spin size="small" /> : <ReloadOutlined />}>
|
||||
Refresh
|
||||
</Button>
|
||||
</Space>
|
||||
<Col span={24} style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setUploadModalVisible(true)}>
|
||||
Upload / New
|
||||
</Button>
|
||||
<Button onClick={handleManualRefresh} icon={loading ? <Spin size="small" /> : <ReloadOutlined />}>
|
||||
Refresh
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -821,6 +913,60 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
|
||||
<PythonEditor value={reqEditorValue} onChange={setReqEditorValue} height={420} />
|
||||
</Modal>
|
||||
|
||||
{/* Add Requirements Modal (for scripts without requirements) */}
|
||||
<Modal
|
||||
open={addReqModalVisible}
|
||||
title={addReqTarget ? `Add requirements — ${addReqTarget}` : 'Add requirements'}
|
||||
onCancel={() => {
|
||||
setAddReqModalVisible(false);
|
||||
setAddReqTarget(null);
|
||||
setAddReqInlineValue('');
|
||||
setAddReqFile(null);
|
||||
setAddReqUseInline(false);
|
||||
}}
|
||||
footer={
|
||||
<Space style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setAddReqModalVisible(false);
|
||||
setAddReqTarget(null);
|
||||
setAddReqInlineValue('');
|
||||
setAddReqFile(null);
|
||||
setAddReqUseInline(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" onClick={saveAddRequirements}>
|
||||
Save requirements
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
width={700}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div>
|
||||
<div style={{ marginBottom: 8 }}>Upload a requirements.txt file (preferred)</div>
|
||||
<Upload beforeUpload={() => false} maxCount={1} accept=".txt" onChange={onAddReqUploadChange}>
|
||||
<Button icon={<UploadOutlined />}>Choose requirements file</Button>
|
||||
</Upload>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Checkbox checked={addReqUseInline} onChange={(e) => setAddReqUseInline(e.target.checked)}>
|
||||
Edit requirements inline
|
||||
</Checkbox>
|
||||
<div style={{ color: '#666' }}>If both a file and inline content are present, file takes precedence.</div>
|
||||
</div>
|
||||
|
||||
{addReqUseInline && (
|
||||
<div>
|
||||
<PythonEditor value={addReqInlineValue} onChange={setAddReqInlineValue} height={220} wrap />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* Pip output modal */}
|
||||
<Modal
|
||||
open={pipModalVisible}
|
||||
|
||||
Reference in New Issue
Block a user