Embed U-Navigator in Dynamix
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
const UNAV_DEFAULT_ROOT = '/mnt/user';
|
||||
|
||||
function unav_roots(): array {
|
||||
$configured = getenv('U_NAV_ROOTS') ?: UNAV_DEFAULT_ROOT;
|
||||
$roots = array_values(array_filter(array_map('trim', explode(',', $configured))));
|
||||
if (!$roots) {
|
||||
$roots = [UNAV_DEFAULT_ROOT];
|
||||
}
|
||||
|
||||
$resolved = [];
|
||||
foreach ($roots as $root) {
|
||||
$real = realpath($root);
|
||||
if ($real !== false && is_dir($real)) {
|
||||
$resolved[] = [
|
||||
'id' => 'root' . (count($resolved) + 1),
|
||||
'label' => $root,
|
||||
'path' => $real,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$resolved) {
|
||||
unav_error(500, 'No usable U-Navigator root found');
|
||||
}
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
function unav_read_only(): bool {
|
||||
return getenv('U_NAV_READ_ONLY') === '1';
|
||||
}
|
||||
|
||||
function unav_json($payload, int $status = 200): void {
|
||||
http_response_code($status);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($payload, JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
function unav_error(int $status, string $message): void {
|
||||
unav_json(['error' => $message], $status);
|
||||
}
|
||||
|
||||
function unav_is_inside(string $path, string $root): bool {
|
||||
if ($path === $root) {
|
||||
return true;
|
||||
}
|
||||
return str_starts_with($path, rtrim($root, '/') . '/');
|
||||
}
|
||||
|
||||
function unav_root_for_real(string $real): array {
|
||||
foreach (unav_roots() as $root) {
|
||||
if (unav_is_inside($real, $root['path'])) {
|
||||
return $root;
|
||||
}
|
||||
}
|
||||
unav_error(403, 'Path is outside allowed roots');
|
||||
}
|
||||
|
||||
function unav_existing_path(?string $input): array {
|
||||
$roots = unav_roots();
|
||||
$candidate = $input ?: $roots[0]['path'];
|
||||
$real = realpath($candidate);
|
||||
if ($real === false) {
|
||||
unav_error(404, 'Path not found');
|
||||
}
|
||||
$root = unav_root_for_real($real);
|
||||
return ['root' => $root, 'path' => $real];
|
||||
}
|
||||
|
||||
function unav_destination_path(?string $input): array {
|
||||
if (!$input) {
|
||||
unav_error(400, 'Missing destination');
|
||||
}
|
||||
$candidate = $input;
|
||||
$parent = dirname($candidate);
|
||||
$parentReal = realpath($parent);
|
||||
if ($parentReal === false) {
|
||||
unav_error(404, 'Destination parent not found');
|
||||
}
|
||||
$root = unav_root_for_real($parentReal);
|
||||
return ['root' => $root, 'path' => $parentReal . '/' . basename($candidate)];
|
||||
}
|
||||
|
||||
function unav_assert_writable(): void {
|
||||
if (unav_read_only()) {
|
||||
unav_error(403, 'Read-only mode is enabled');
|
||||
}
|
||||
}
|
||||
|
||||
function unav_request_json(): array {
|
||||
$raw = file_get_contents('php://input');
|
||||
$data = json_decode($raw ?: '{}', true);
|
||||
if (!is_array($data)) {
|
||||
unav_error(400, 'Invalid JSON body');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
function unav_copy_recursive(string $source, string $destination): void {
|
||||
if (is_link($source)) {
|
||||
unav_error(400, 'Copying symlinks is not allowed');
|
||||
}
|
||||
if (is_dir($source)) {
|
||||
if (!mkdir($destination, 0777, true) && !is_dir($destination)) {
|
||||
unav_error(500, 'Could not create destination directory');
|
||||
}
|
||||
foreach (scandir($source) ?: [] as $entry) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
unav_copy_recursive($source . '/' . $entry, $destination . '/' . $entry);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!is_file($source)) {
|
||||
unav_error(400, 'Only files and directories can be copied');
|
||||
}
|
||||
if (!copy($source, $destination)) {
|
||||
unav_error(500, 'Copy failed');
|
||||
}
|
||||
}
|
||||
|
||||
function unav_guard_destination(string $source, string $destination): void {
|
||||
if (file_exists($destination)) {
|
||||
unav_error(409, 'Destination already exists');
|
||||
}
|
||||
if ($source === $destination || str_starts_with($destination, rtrim($source, '/') . '/')) {
|
||||
unav_error(400, 'Destination cannot be inside source');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/common.php';
|
||||
|
||||
unav_json([
|
||||
'readOnly' => unav_read_only(),
|
||||
'roots' => unav_roots(),
|
||||
]);
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/common.php';
|
||||
|
||||
unav_assert_writable();
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
unav_json([
|
||||
'id' => $_GET['id'] ?? '0',
|
||||
'type' => 'unknown',
|
||||
'status' => 'completed',
|
||||
'progress' => 100,
|
||||
'message' => 'Completed',
|
||||
'error' => null,
|
||||
'createdAt' => date(DATE_ATOM),
|
||||
'updatedAt' => date(DATE_ATOM),
|
||||
]);
|
||||
}
|
||||
|
||||
$data = unav_request_json();
|
||||
$source = unav_existing_path($data['source'] ?? null);
|
||||
$destination = unav_destination_path($data['destination'] ?? null);
|
||||
unav_guard_destination($source['path'], $destination['path']);
|
||||
|
||||
if ($action === 'move') {
|
||||
if (!rename($source['path'], $destination['path'])) {
|
||||
unav_error(500, 'Move failed');
|
||||
}
|
||||
} elseif ($action === 'copy') {
|
||||
unav_copy_recursive($source['path'], $destination['path']);
|
||||
} else {
|
||||
unav_error(404, 'Unknown job action');
|
||||
}
|
||||
|
||||
unav_json([
|
||||
'id' => (string)time(),
|
||||
'type' => $action,
|
||||
'status' => 'completed',
|
||||
'progress' => 100,
|
||||
'message' => 'Completed',
|
||||
'error' => null,
|
||||
'createdAt' => date(DATE_ATOM),
|
||||
'updatedAt' => date(DATE_ATOM),
|
||||
], 202);
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/common.php';
|
||||
|
||||
$resolved = unav_existing_path($_GET['path'] ?? null);
|
||||
if (!is_dir($resolved['path'])) {
|
||||
unav_error(400, 'Path is not a directory');
|
||||
}
|
||||
|
||||
$entries = [];
|
||||
foreach (scandir($resolved['path']) ?: [] as $name) {
|
||||
if ($name === '.' || $name === '..') {
|
||||
continue;
|
||||
}
|
||||
$full = $resolved['path'] . '/' . $name;
|
||||
$type = is_link($full) ? 'symlink' : (is_dir($full) ? 'directory' : (is_file($full) ? 'file' : 'other'));
|
||||
$entries[] = [
|
||||
'name' => $name,
|
||||
'path' => $full,
|
||||
'type' => $type,
|
||||
'size' => is_file($full) ? filesize($full) : 0,
|
||||
'modifiedAt' => date(DATE_ATOM, filemtime($full) ?: time()),
|
||||
];
|
||||
}
|
||||
|
||||
usort($entries, function ($a, $b) {
|
||||
if ($a['type'] === 'directory' && $b['type'] !== 'directory') return -1;
|
||||
if ($a['type'] !== 'directory' && $b['type'] === 'directory') return 1;
|
||||
return strnatcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
|
||||
unav_json([
|
||||
'path' => $resolved['path'],
|
||||
'entries' => $entries,
|
||||
]);
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/common.php';
|
||||
|
||||
unav_assert_writable();
|
||||
$target = unav_existing_path($_GET['path'] ?? null);
|
||||
if (!is_dir($target['path'])) {
|
||||
unav_error(400, 'Upload target must be a directory');
|
||||
}
|
||||
|
||||
$uploaded = [];
|
||||
foreach ($_FILES['files']['name'] ?? [] as $index => $name) {
|
||||
if ($_FILES['files']['error'][$index] !== UPLOAD_ERR_OK) {
|
||||
unav_error(400, 'Upload failed');
|
||||
}
|
||||
$safeName = basename($name);
|
||||
$destination = unav_destination_path($target['path'] . '/' . $safeName);
|
||||
if (file_exists($destination['path'])) {
|
||||
unav_error(409, 'Destination already exists');
|
||||
}
|
||||
if (!move_uploaded_file($_FILES['files']['tmp_name'][$index], $destination['path'])) {
|
||||
unav_error(500, 'Could not store uploaded file');
|
||||
}
|
||||
$uploaded[] = [
|
||||
'name' => $safeName,
|
||||
'size' => filesize($destination['path']) ?: 0,
|
||||
];
|
||||
}
|
||||
|
||||
unav_json(['uploaded' => $uploaded], 201);
|
||||
Reference in New Issue
Block a user