Compare commits

..

2 Commits

Author SHA1 Message Date
ede4b3e78d script protection update
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 0s
Build and Deploy MITM Webserver / build (push) Successful in 1m41s
2026-04-17 21:44:28 +02:00
ead2d5f217 remove disable script protection 2026-04-17 21:40:36 +02:00
6 changed files with 81 additions and 43 deletions

View File

@@ -9,7 +9,7 @@ Files in this folder are treated as protected example scripts by the API:
Protected behavior: Protected behavior:
- scripts are synced from this folder into `/srv/fw-scripts` on backend startup - scripts are synced from this folder into `/srv/fw-scripts` on backend startup
- scripts in this folder cannot be overwritten, disabled, or deleted via the API - scripts in this folder cannot be overwritten, edited, or deleted via the API
- scripts with a deploy config containing `qnum` are auto-started as systemd services - scripts with a deploy config containing `qnum` are auto-started as systemd services
Example deploy file: Example deploy file:

View File

@@ -1,4 +0,0 @@
{
"qnum": 1,
"enable_at_boot": true
}

View File

@@ -926,8 +926,6 @@ def enable_script(name: str, req: EnableRequest) -> OperationResult:
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
assert_not_example_script(name, "manually enabled")
script_path = script_path_for(name) script_path = script_path_for(name)
if not os.path.exists(script_path): if not os.path.exists(script_path):
raise HTTPException(status_code=404, detail="script not found") raise HTTPException(status_code=404, detail="script not found")
@@ -968,8 +966,6 @@ def disable_script(name: str, qnum: int) -> OperationResult:
validate_name(name) validate_name(name)
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
assert_not_example_script(name, "disabled")
service_name = make_service_name(name, qnum) service_name = make_service_name(name, qnum)
# attempt stop + remove via systemctl-based remove_unit # attempt stop + remove via systemctl-based remove_unit
try: try:
@@ -1102,4 +1098,4 @@ def register_lifecycle(app) -> None:
remove_unit(svc) remove_unit(svc)
except Exception: except Exception:
logger.exception("Failed to remove unit %s on shutdown", svc) logger.exception("Failed to remove unit %s on shutdown", svc)
logger.info("Shutdown cleanup complete") logger.info("Shutdown cleanup complete")

View File

@@ -10,6 +10,7 @@ import {
UploadOutlined, UploadOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { import {
Badge,
Button, Button,
Checkbox, Checkbox,
Col, Col,
@@ -484,6 +485,12 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
setAddReqModalVisible(true); setAddReqModalVisible(true);
}, []); }, []);
const renderStatus = useCallback((isActive: boolean) => {
return <Badge color={isActive ? '#52c41a' : '#ff4d4f'} text={isActive ? 'Active' : 'Inactive'} />;
}, []);
const isScriptActive = useCallback((record: ScriptWithStatus) => record.mappings.some((mapping) => mapping.active), []);
const saveAddRequirements = useCallback(async () => { const saveAddRequirements = useCallback(async () => {
if (!addReqTarget) return; if (!addReqTarget) return;
let payload: File | Blob | null = null; let payload: File | Blob | null = null;
@@ -529,7 +536,18 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
title: 'Name', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
render: (text) => <code>{text}</code>, render: (_: unknown, record: ScriptWithStatus) => (
<Space>
<code>{record.name}</code>
{record.is_protected_example ? <Badge color="#1677ff" text="Protected" /> : null}
</Space>
),
},
{
title: 'Status',
key: 'status',
width: 120,
render: (_: unknown, record: ScriptWithStatus) => renderStatus(isScriptActive(record)),
}, },
{ {
title: 'Path', title: 'Path',
@@ -540,7 +558,7 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
{ {
title: 'Requirements', title: 'Requirements',
key: 'requirements', key: 'requirements',
render: (_: any, record: ScriptWithStatus) => render: (_: unknown, record: ScriptWithStatus) =>
record.requirements_exists ? ( record.requirements_exists ? (
<Space> <Space>
<IconButtonTooltip <IconButtonTooltip
@@ -548,25 +566,33 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
onClick={() => handleDownloadRequirements(record.name)} onClick={() => handleDownloadRequirements(record.name)}
icon={<DownloadOutlined />} icon={<DownloadOutlined />}
/> />
<IconButtonTooltip {!record.requirements_is_protected_example ? (
title="Edit requirements" <IconButtonTooltip
onClick={() => handleEditRequirements(record.name)} title="Edit requirements"
icon={<EditOutlined />} onClick={() => handleEditRequirements(record.name)}
/> icon={<EditOutlined />}
<IconButtonTooltip />
title="Delete requirements" ) : null}
danger {!record.requirements_is_protected_example ? (
onClick={() => handleDeleteRequirements(record.name)} <IconButtonTooltip
icon={<DeleteOutlined />} title="Delete requirements"
/> danger
onClick={() => handleDeleteRequirements(record.name)}
icon={<DeleteOutlined />}
/>
) : null}
</Space> </Space>
) : ( ) : (
<Space> <Space>
<IconButtonTooltip {!record.is_protected_example ? (
title="Add requirements" <IconButtonTooltip
onClick={() => openAddRequirements(record.name)} title="Add requirements"
icon={<FileAddOutlined />} onClick={() => openAddRequirements(record.name)}
/> icon={<FileAddOutlined />}
/>
) : (
<span style={{ color: '#8c8c8c' }}>Protected</span>
)}
</Space> </Space>
), ),
}, },
@@ -574,18 +600,20 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
title: 'Actions', title: 'Actions',
key: 'actions', key: 'actions',
width: 240, width: 240,
render: (_: any, record: ScriptWithStatus) => ( render: (_: unknown, record: ScriptWithStatus) => (
<Space> <Space>
<IconButtonTooltip <IconButtonTooltip
title="Download script" title="Download script"
onClick={() => handleDownload(record.name)} onClick={() => handleDownload(record.name)}
icon={<DownloadOutlined />} icon={<DownloadOutlined />}
/> />
<IconButtonTooltip {!record.is_protected_example ? (
title="Open in editor" <IconButtonTooltip
onClick={() => openInEditorFromServer(record.name)} title="Open in editor"
icon={<EditOutlined />} onClick={() => openInEditorFromServer(record.name)}
/> icon={<EditOutlined />}
/>
) : null}
<IconButtonTooltip <IconButtonTooltip
title="Enable" title="Enable"
onClick={() => { onClick={() => {
@@ -594,17 +622,28 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
}} }}
icon={<PlayCircleOutlined />} icon={<PlayCircleOutlined />}
/> />
<Popconfirm title={`Delete ${record.name}?`} onConfirm={() => handleDelete(record.name)}> {!record.is_protected_example ? (
<Tooltip title="Delete script"> <Popconfirm title={`Delete ${record.name}?`} onConfirm={() => handleDelete(record.name)}>
<Button danger size="small" icon={<DeleteOutlined />} /> <Tooltip title="Delete script">
</Tooltip> <Button danger size="small" icon={<DeleteOutlined />} />
</Popconfirm> </Tooltip>
</Popconfirm>
) : null}
</Space> </Space>
), ),
}, },
], ],
// eslint-disable-next-line react-hooks/exhaustive-deps [
[], handleDelete,
handleDeleteRequirements,
handleDownload,
handleDownloadRequirements,
handleEditRequirements,
isScriptActive,
openAddRequirements,
openInEditorFromServer,
renderStatus,
],
); );
const nestedColumns = useMemo( const nestedColumns = useMemo(
@@ -621,7 +660,12 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
key: 'qnum', key: 'qnum',
render: (q: number | null) => (q == null ? '-' : q), render: (q: number | null) => (q == null ? '-' : q),
}, },
{ title: 'Active', dataIndex: 'active', key: 'active', render: (a: boolean) => (a ? 'yes' : 'no') }, {
title: 'Status',
dataIndex: 'active',
key: 'active',
render: (active: boolean) => renderStatus(active),
},
{ title: 'Extra', dataIndex: ['parsed', 'extra'], key: 'extra', render: (e: string | null) => e || '-' }, { title: 'Extra', dataIndex: ['parsed', 'extra'], key: 'extra', render: (e: string | null) => e || '-' },
{ {
title: 'ExecStart', title: 'ExecStart',
@@ -645,7 +689,7 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c
), ),
}, },
], ],
[handleDisableInstance], [handleDisableInstance, renderStatus],
); );
const expandable = useMemo( const expandable = useMemo(

View File

@@ -31,6 +31,8 @@ export type StatusForNameResponse = {
export type ScriptWithStatus = ScriptInfo & { export type ScriptWithStatus = ScriptInfo & {
mappings: UnitMapping[]; mappings: UnitMapping[];
requirements_exists: boolean; requirements_exists: boolean;
is_protected_example: boolean;
requirements_is_protected_example: boolean;
}; };
export type ScriptUploadResponse = ScriptInfo & { export type ScriptUploadResponse = ScriptInfo & {