feat: enhance volume management with support for multiple volumes and improved API endpoints
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
9b0c3aa190
commit
e7c0b56473
3 changed files with 133 additions and 41 deletions
52
panel-api.py
52
panel-api.py
|
|
@ -48,6 +48,16 @@ def parse_env_blob(blob):
|
|||
return out
|
||||
|
||||
|
||||
def get_app_volumes(name):
|
||||
result = run_panelctl(["inspect-volumes", name])
|
||||
volumes = {}
|
||||
if result["ok"]:
|
||||
for line in result["stdout"].splitlines():
|
||||
if "|" in line:
|
||||
vname, vpath = line.split("|", 1)
|
||||
volumes[vname.strip()] = vpath.strip()
|
||||
return volumes
|
||||
|
||||
def read_app_info(name):
|
||||
if not is_safe_name(name):
|
||||
return None, {"ok": False, "error": "invalid app name"}
|
||||
|
|
@ -311,6 +321,16 @@ class Handler(BaseHTTPRequestHandler):
|
|||
self._json(200, {"ok": True, "app": parse_env_blob(result["stdout"])})
|
||||
return
|
||||
|
||||
# /apps/<name>/volumes
|
||||
if len(parts) == 3 and parts[0] == "apps" and parts[2] == "volumes":
|
||||
name = parts[1]
|
||||
if not is_safe_name(name):
|
||||
self._json(400, {"ok": False, "error": "invalid app name"})
|
||||
return
|
||||
volumes = get_app_volumes(name)
|
||||
self._json(200, {"ok": True, "name": name, "volumes": volumes})
|
||||
return
|
||||
|
||||
# /apps/<name>/volume/files
|
||||
if len(parts) == 4 and parts[0] == "apps" and parts[2] == "volume" and parts[3] == "files":
|
||||
name = parts[1]
|
||||
|
|
@ -319,8 +339,14 @@ class Handler(BaseHTTPRequestHandler):
|
|||
self._json(404, err)
|
||||
return
|
||||
|
||||
volumes = get_app_volumes(name)
|
||||
vol_key = query.get("vol", ["default"])[0]
|
||||
if vol_key not in volumes:
|
||||
self._json(400, {"ok": False, "error": "invalid volume specified"})
|
||||
return
|
||||
|
||||
subpath = query.get("path", [""])[0].strip("/")
|
||||
data_dir = os.path.join(app["APP_VOLUME_DIR"], "data")
|
||||
data_dir = volumes[vol_key]
|
||||
target_dir = os.path.abspath(os.path.join(data_dir, subpath))
|
||||
|
||||
# Ensure traversal didn't escape data_dir
|
||||
|
|
@ -364,12 +390,18 @@ class Handler(BaseHTTPRequestHandler):
|
|||
self._json(404, err)
|
||||
return
|
||||
|
||||
volumes = get_app_volumes(name)
|
||||
vol_key = query.get("vol", ["default"])[0]
|
||||
if vol_key not in volumes:
|
||||
self._json(400, {"ok": False, "error": "invalid volume specified"})
|
||||
return
|
||||
|
||||
subpath = query.get("path", [""])[0].strip("/")
|
||||
if not subpath:
|
||||
self._json(400, {"ok": False, "error": "path parameter required"})
|
||||
return
|
||||
|
||||
data_dir = os.path.join(app["APP_VOLUME_DIR"], "data")
|
||||
data_dir = volumes[vol_key]
|
||||
target_file = os.path.abspath(os.path.join(data_dir, subpath))
|
||||
|
||||
if not target_file.startswith(os.path.abspath(data_dir)):
|
||||
|
|
@ -406,12 +438,18 @@ class Handler(BaseHTTPRequestHandler):
|
|||
self._json(404, err)
|
||||
return
|
||||
|
||||
volumes = get_app_volumes(name)
|
||||
vol_key = query.get("vol", ["default"])[0]
|
||||
if vol_key not in volumes:
|
||||
self._json(400, {"ok": False, "error": "invalid volume specified"})
|
||||
return
|
||||
|
||||
subpath = query.get("path", [""])[0].strip("/")
|
||||
if not subpath:
|
||||
self._json(400, {"ok": False, "error": "path parameter required"})
|
||||
return
|
||||
|
||||
data_dir = os.path.join(app["APP_VOLUME_DIR"], "data")
|
||||
data_dir = volumes[vol_key]
|
||||
target_file = os.path.abspath(os.path.join(data_dir, subpath))
|
||||
if not target_file.startswith(os.path.abspath(data_dir)):
|
||||
self._json(403, {"ok": False, "error": "path traversal denied"})
|
||||
|
|
@ -446,12 +484,18 @@ class Handler(BaseHTTPRequestHandler):
|
|||
self._json(404, err)
|
||||
return
|
||||
|
||||
volumes = get_app_volumes(name)
|
||||
vol_key = query.get("vol", ["default"])[0]
|
||||
if vol_key not in volumes:
|
||||
self._json(400, {"ok": False, "error": "invalid volume specified"})
|
||||
return
|
||||
|
||||
subpath = query.get("path", [""])[0].strip("/")
|
||||
if not subpath:
|
||||
self._json(400, {"ok": False, "error": "path parameter required"})
|
||||
return
|
||||
|
||||
data_dir = os.path.join(app["APP_VOLUME_DIR"], "data")
|
||||
data_dir = volumes[vol_key]
|
||||
target_file = os.path.abspath(os.path.join(data_dir, subpath))
|
||||
|
||||
if not target_file.startswith(os.path.abspath(data_dir)):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue