Jobs interface is looking really good now..
This commit is contained in:
@@ -7,6 +7,7 @@ import logging
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import gzip
|
import gzip
|
||||||
|
import shlex
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
|
||||||
from packetserver.http.dependencies import get_current_http_user
|
from packetserver.http.dependencies import get_current_http_user
|
||||||
@@ -20,6 +21,16 @@ from packetserver.server.jobs import RunnerFile
|
|||||||
router = APIRouter(prefix="/api/v1", tags=["jobs"])
|
router = APIRouter(prefix="/api/v1", tags=["jobs"])
|
||||||
dashboard_router = APIRouter(tags=["jobs"])
|
dashboard_router = APIRouter(tags=["jobs"])
|
||||||
|
|
||||||
|
def tokenize_cmd(cmd: str) -> list[str]:
|
||||||
|
"""
|
||||||
|
Tokenize a command string with basic shell-like quoting support.
|
||||||
|
Uses shlex with posix=True for proper "double" and 'single' quote handling.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return shlex.split(cmd)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Invalid command quoting: {e}")
|
||||||
|
|
||||||
class JobSummary(BaseModel):
|
class JobSummary(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
cmd: Union[str, List[str]]
|
cmd: Union[str, List[str]]
|
||||||
@@ -161,6 +172,7 @@ async def create_job_from_form(
|
|||||||
env_values: List[str] = Form(default=[]),
|
env_values: List[str] = Form(default=[]),
|
||||||
files: List[UploadFile] = File(default=[]),
|
files: List[UploadFile] = File(default=[]),
|
||||||
include_db: Optional[str] = Form(None),
|
include_db: Optional[str] = Form(None),
|
||||||
|
shell_mode: Optional[str] = Form(None),
|
||||||
current_user: HttpUser = Depends(get_current_http_user)
|
current_user: HttpUser = Depends(get_current_http_user)
|
||||||
):
|
):
|
||||||
# Build env dict from parallel lists
|
# Build env dict from parallel lists
|
||||||
@@ -176,6 +188,15 @@ async def create_job_from_form(
|
|||||||
content = await upload.read()
|
content = await upload.read()
|
||||||
files_dict[upload.filename] = base64.b64encode(content).decode('ascii')
|
files_dict[upload.filename] = base64.b64encode(content).decode('ascii')
|
||||||
|
|
||||||
|
if shell_mode == "on":
|
||||||
|
# Pass entire raw cmd string to bash -c
|
||||||
|
cmd_args = ["bash", "-c", cmd.strip()]
|
||||||
|
else:
|
||||||
|
# Use shlex to respect quotes
|
||||||
|
cmd_args = tokenize_cmd(cmd.strip())
|
||||||
|
if not cmd_args:
|
||||||
|
raise HTTPException(status_code=400, detail="Command cannot be empty")
|
||||||
|
|
||||||
if include_db == "on":
|
if include_db == "on":
|
||||||
try:
|
try:
|
||||||
username_lower = current_user.username.lower()
|
username_lower = current_user.username.lower()
|
||||||
@@ -191,14 +212,13 @@ async def create_job_from_form(
|
|||||||
|
|
||||||
# Prepare payload for the existing API
|
# Prepare payload for the existing API
|
||||||
payload = {
|
payload = {
|
||||||
"cmd": [part.strip() for part in cmd.split() if part.strip()], # split on whitespace, like shell
|
"cmd": cmd_args,
|
||||||
"env": env if env else None,
|
"env": env if env else None,
|
||||||
"files": files_dict if files_dict else None
|
"files": files_dict if files_dict else None
|
||||||
}
|
}
|
||||||
|
|
||||||
# Call the API internally
|
# Call the API internally
|
||||||
from packetserver.http.routers.jobs import create_job as api_create_job
|
response = await create_job(
|
||||||
response = await api_create_job(
|
|
||||||
payload=JobCreate(**{k: v for k, v in payload.items() if v is not None}),
|
payload=JobCreate(**{k: v for k, v in payload.items() if v is not None}),
|
||||||
db=db,
|
db=db,
|
||||||
current_user=current_user
|
current_user=current_user
|
||||||
|
|||||||
@@ -10,9 +10,33 @@
|
|||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Command
|
Command
|
||||||
|
{% if job.cmd|length > 1 or ('bash' in job.cmd and job.cmd|length == 3) %}
|
||||||
|
<span class="badge bg-info float-end">Multi-arg</span>
|
||||||
|
{% elif job.cmd|length == 1 %}
|
||||||
|
<span class="badge bg-secondary float-end">Single arg</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<pre><code>{% if job.cmd is string %}{{ job.cmd }}{% else %}{{ job.cmd|join(' ') }}{% endif %}</code></pre>
|
{% if job.cmd is string %}
|
||||||
|
<!-- Legacy fallback: old jobs stored cmd as single string -->
|
||||||
|
<pre><code>{{ job.cmd }}</code></pre>
|
||||||
|
<small class="text-muted">Legacy single-string command</small>
|
||||||
|
{% else %}
|
||||||
|
<!-- Modern list display -->
|
||||||
|
<ol class="mb-0 ps-4">
|
||||||
|
{% for arg in job.cmd %}
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>{{ arg | e }}</code>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
{% if job.cmd[:2] == ['bash', '-c'] %}
|
||||||
|
<hr class="my-3">
|
||||||
|
<p class="mb-0"><strong>Full command passed to bash -c:</strong></p>
|
||||||
|
<pre><code>{{ job.cmd[2] }}</code></pre>
|
||||||
|
<small class="text-muted">This job used shell mode</small>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,16 @@
|
|||||||
<textarea name="cmd" class="form-control" rows="3" placeholder="e.g. python script.py --input data.txt" required></textarea>
|
<textarea name="cmd" class="form-control" rows="3" placeholder="e.g. python script.py --input data.txt" required></textarea>
|
||||||
<div class="form-text">Space-separated command and arguments (like a shell command).</div>
|
<div class="form-text">Space-separated command and arguments (like a shell command).</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="shell_mode" name="shell_mode" value="on">
|
||||||
|
<label class="form-check-label" for="shell_mode">
|
||||||
|
Run command through shell (bash -c)
|
||||||
|
<small class="text-muted d-block">
|
||||||
|
Allows full shell syntax, pipes, redirects, globbing, and quoted arguments with spaces.
|
||||||
|
The entire command will be passed as a single string to <code>bash -c</code>.
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Environment Variables</label>
|
<label class="form-label">Environment Variables</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user