Refine admin status and notification controls

This commit is contained in:
hbrain 2026-05-26 20:31:04 +02:00
parent 4e4def843d
commit 5437c7a4c4
5 changed files with 15 additions and 9 deletions

View file

@ -6,5 +6,5 @@ return [
"uploads_dir" => __DIR__ . "/in-dir",
"public_videos" => "out-dir",
"public_thumbs" => "thumbs",
"items_per_page" => 12,
"items_per_page" => 8,
];

View file

@ -126,7 +126,7 @@ $page=max(1,(int)($_GET['page']??1)); $per=$config['items_per_page']; $total=cou
}
await state(reg);
}catch(e){ alert(e.message || 'Could not update notifications'); }
finally{ btn.disabled=false; }
finally{ await state(reg); }
});
});
})();

View file

@ -13,7 +13,7 @@ foreach ($dirs as $dir) {
$state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
$status = is_array($state) ? (string)($state['status'] ?? '') : '';
$locked = is_dir($lockDir);
if ($locked || in_array($status, ['processing', 'stale'], true)) {
if ($locked || $status === 'processing') {
$started = '';
if (is_file($lockDir . '/started_at')) $started = trim((string)file_get_contents($lockDir . '/started_at'));
if ($started === '' && is_array($state)) $started = (string)($state['started_at'] ?? $state['updated_at'] ?? '');

16
new.php
View file

@ -202,9 +202,15 @@ function input_dir_status($dir){
$state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
$status = is_array($state) ? (string)($state['status'] ?? '') : '';
if (is_dir($lockDir)) return $status !== '' ? $status : 'processing';
return in_array($status, ['processing', 'stale'], true) ? $status : '';
return in_array($status, ['processing', 'stale', 'error'], true) ? $status : '';
}
function input_dir_running($dir){
$stateFile = $dir . '/.movmaker-state.json';
$lockDir = $dir . '/.movmaker-lock';
$state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
$status = is_array($state) ? (string)($state['status'] ?? '') : '';
return is_dir($lockDir) || $status === 'processing';
}
function input_dir_running($dir){ return input_dir_status($dir) !== ''; }
function input_dir_enabled($dir){ return is_file($dir . '/.movmaker-enabled'); }
function set_input_dir_enabled($dir, $enabled){
$path = $dir . '/.movmaker-enabled';
@ -232,7 +238,7 @@ function cached_video_metadata($output, $fallback){
}
return $meta;
}
function paginate($items, $page, $perPage = 10){
function paginate($items, $page, $perPage = 8){
$total = count($items);
$pages = max(1, (int)ceil($total / $perPage));
$page = max(1, min((int)$page, $pages));
@ -355,14 +361,14 @@ $page = max(1, (int)($_GET['page'] ?? 1));
[$videoPage, $videoPages, $orphanVideosPage, $orphanVideosTotal] = paginate($orphanVideos, $tab === 'videos' ? $page : 1);
$editName = $_GET['edit'] ?? '';
$editDir = null; $editStatus = ''; $editRunning = false; $editData = ['title'=>'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $editFiles = [];
if ($editName !== '') { try { $editDir = safe_input_dir($config['uploads_dir'], $editName); $editStatus = input_dir_status($editDir); $editRunning = $editStatus !== ''; if (!$editRunning) { $editData = read_data($editDir); $editFiles = media_sort_files($editDir, list_files($editDir)); } } catch (Throwable $e) { $err = $e->getMessage(); } }
if ($editName !== '') { try { $editDir = safe_input_dir($config['uploads_dir'], $editName); $editStatus = input_dir_status($editDir); $editRunning = input_dir_running($editDir); if (!$editRunning) { $editData = read_data($editDir); $editFiles = media_sort_files($editDir, list_files($editDir)); } } catch (Throwable $e) { $err = $e->getMessage(); } }
?>
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Admin - <?=h($config['site_name'])?></title><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css"></head><body>
<header class="site-header admin-header"><div class="admin-left"><nav class="tabs"><a class="<?= $tab==='new'?'active':'' ?>" href="new.php?tab=new">New</a><a class="<?= $tab==='edit'?'active':'' ?>" href="new.php?tab=edit">Edit</a><a class="<?= $tab==='videos'?'active':'' ?>" href="new.php?tab=videos">Videos</a></nav></div><div class="brand-wrap"><a class="header-logo" href="index.php" aria-label="MVLog home"><img src="assets/img/moto_travel.png" alt=""></a><a class="brand" href="index.php"><h1>MVLog Admin</h1><p>Bubulescu.Org</p></a></div><div class="admin-right"><div id="job-status" class="job-status" hidden></div><a class="logout-link" href="logout.php">Log off</a></div></header><main>
<?php if($msg): ?><div class="ok"><?=h($msg)?></div><?php endif; ?><?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?>
<?php if($tab==='videos'): ?><section><h2>Videos without input dir</h2><?php if(!$orphanVideos): ?><p>No orphan videos.</p><?php endif; ?><div class="admin-list"><?php foreach($orphanVideosPage as $v): $vn=basename($v); ?><form method="post" class="admin-item" onsubmit="return confirm('Delete this generated video?')"><div><strong><?=h($vn)?></strong><br><span><?=h(round(filesize($v)/1048576,1))?> MB</span></div><input type="hidden" name="action" value="delete_video"><input type="hidden" name="video" value="<?=h($vn)?>"><button type="submit">Delete video</button></form><?php endforeach; ?></div><?=page_links('videos',$videoPage,$videoPages)?></section><?php endif; ?>
<?php if($tab==='edit'): ?><section><h2>Existing input dirs</h2><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list video-list"><?php foreach($dirsPage as $d): $runStatus=input_dir_status($d); $running=$runStatus !== ''; $enabled=input_dir_enabled($d); $visible=input_dir_visible($d); $info=$dirInfo[basename($d)] ?? input_dir_info($d); $data=read_data($d); $output=input_dir_output($d); $hasVideo=$output !== '' && is_file($config['videos_dir'].'/'.$output); $meta=cached_video_metadata($output, $data); ?><article class="video-row admin-video-row"><div><?php if($hasVideo): ?><video controls controlsList="nodownload" oncontextmenu="return false" preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($output))?>"></video><?php else: ?><div class="video-placeholder"><img src="assets/img/moto_travel.png" alt=""><span>No video yet</span></div><?php endif; ?></div><div class="video-info"><div class="admin-row-top"><div class="admin-switches"><form method="post" class="inline-form"><input type="hidden" name="action" value="set_visible"><input type="hidden" name="dir" value="<?=h(basename($d))?>"><label class="switch-label"><input type="checkbox" name="visible" value="1" onchange="this.form.submit()" <?=$visible?'checked':''?>><span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Show</span></label></form><form method="post" class="inline-form"><input type="hidden" name="action" value="set_enabled"><input type="hidden" name="dir" value="<?=h(basename($d))?>"><label class="switch-label"><input type="checkbox" name="enabled" value="1" onchange="this.form.submit()" <?=$enabled?'checked':''?>><span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Run</span></label></form></div><div class="admin-actions"><?php if($running): ?><span class="button disabled" title="Rendering now"><?=h(ucfirst($runStatus))?></span><?php else: ?><a class="button" href="?edit=<?=rawurlencode(basename($d))?>">Edit</a><?php endif; ?></div></div><h2><?=h($meta['title'] ?: ($info['title'] ?? basename($d)))?></h2><p class="meta"><?php if($meta['date']): ?><span><?=h($meta['date'])?></span><?php endif; ?><?php if($meta['location']): ?><span><?=h($meta['location'])?></span><?php endif; ?><?php if(!$hasVideo): ?><span>No video</span><?php endif; ?></p><?php if($meta['description']): ?><p class="description"><?=nl2br(h($meta['description']), false)?></p><?php endif; ?><p class="details"><?=h(basename($d))?> · <?=h($info['file_count'] ?? count(list_files($d)))?> files · <?= $enabled ? 'Rendering enabled' : 'Rendering disabled' ?> · <?= $visible ? 'Shown' : 'Hidden' ?><?= $running ? ' · '.h(ucfirst($runStatus)) : '' ?></p></div></article><?php endforeach; ?></div><?=page_links('edit',$editPage,$editPages)?></section><?php endif; ?>
<?php if($tab==='edit'): ?><section><h2>Existing input dirs</h2><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list video-list"><?php foreach($dirsPage as $d): $runStatus=input_dir_status($d); $running=input_dir_running($d); $enabled=input_dir_enabled($d); $visible=input_dir_visible($d); $info=$dirInfo[basename($d)] ?? input_dir_info($d); $data=read_data($d); $output=input_dir_output($d); $hasVideo=$output !== '' && is_file($config['videos_dir'].'/'.$output); $meta=cached_video_metadata($output, $data); ?><article class="video-row admin-video-row"><div><?php if($hasVideo): ?><video controls controlsList="nodownload" oncontextmenu="return false" preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($output))?>"></video><?php else: ?><div class="video-placeholder"><img src="assets/img/moto_travel.png" alt=""><span>No video yet</span></div><?php endif; ?></div><div class="video-info"><div class="admin-row-top"><div class="admin-switches"><form method="post" class="inline-form"><input type="hidden" name="action" value="set_visible"><input type="hidden" name="dir" value="<?=h(basename($d))?>"><label class="switch-label"><input type="checkbox" name="visible" value="1" onchange="this.form.submit()" <?=$visible?'checked':''?>><span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Show</span></label></form><form method="post" class="inline-form"><input type="hidden" name="action" value="set_enabled"><input type="hidden" name="dir" value="<?=h(basename($d))?>"><label class="switch-label"><input type="checkbox" name="enabled" value="1" onchange="this.form.submit()" <?=$enabled?'checked':''?>><span class="switch-ui" aria-hidden="true"></span><span class="switch-text">Run</span></label></form></div><div class="admin-actions"><?php if($running): ?><span class="button disabled" title="Rendering now"><?=h(ucfirst($runStatus))?></span><?php else: ?><a class="button" href="?edit=<?=rawurlencode(basename($d))?>">Edit</a><?php endif; ?></div></div><h2><?=h($meta['title'] ?: ($info['title'] ?? basename($d)))?></h2><p class="meta"><?php if($meta['date']): ?><span><?=h($meta['date'])?></span><?php endif; ?><?php if($meta['location']): ?><span><?=h($meta['location'])?></span><?php endif; ?><?php if(!$hasVideo): ?><span>No video</span><?php endif; ?></p><?php if($meta['description']): ?><p class="description"><?=nl2br(h($meta['description']), false)?></p><?php endif; ?><p class="details"><?=h(basename($d))?> · <?=h($info['file_count'] ?? count(list_files($d)))?> files · <?= $enabled ? 'Rendering enabled' : 'Rendering disabled' ?> · <?= $visible ? 'Shown' : 'Hidden' ?><?= $running ? ' · '.h(ucfirst($runStatus)) : '' ?></p></div></article><?php endforeach; ?></div><?=page_links('edit',$editPage,$editPages)?></section><?php endif; ?>
<?php if($tab==='edit' && $editDir && $editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><div class="err">This input directory is <?=h($editStatus)?>. Editing is disabled until the job completes.</div></section><?php endif; ?>
<?php if($tab==='edit' && $editDir && !$editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="update"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><label>Title<input name="title" value="<?=h($editData['title'])?>"></label><label>Date<input name="date" value="<?=h($editData['date'])?>"></label><label>Place<input name="place" value="<?=h($editData['place'])?>"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-edit"><?=h($editData['description'])?></textarea><small id="description-counter-edit" class="counter"></small></label><label>Add images / videos<input type="file" name="media[]" multiple accept="image/*,video/*"></label><label>Add audio<input type="file" name="audio[]" multiple accept="audio/*"></label><h3>Files and captions</h3><div class="file-list"><?php foreach($editFiles as $f): $ext=strtolower(pathinfo($f, PATHINFO_EXTENSION)); $fileUrl='in-dir/'.rawurlencode(basename($editDir)).'/'.rawurlencode($f); ?><div class="caption-row"><div class="preview"><?php if(in_array($ext,['jpg','jpeg','png','webp','gif'])): ?><img src="<?=h($fileUrl)?>" alt=""><?php elseif(in_array($ext,['mp4','mov','m4v','webm'])): ?><video src="<?=h($fileUrl)?>" muted preload="metadata"></video><?php else: ?><span><?=h(strtoupper($ext ?: 'FILE'))?></span><?php endif; ?></div><div class="caption-fields"><strong><?=h($f)?></strong><?php if(in_array($ext,['jpg','jpeg','png','webp','gif','mp4','mov','m4v','webm'])): ?><input type="hidden" name="caption_files[]" value="<?=h($f)?>"><textarea name="captions[]" placeholder="Optional caption for this file"><?=h($editData['captions'][$f] ?? '')?></textarea><?php if(in_array($ext,['mp4','mov','m4v','webm'])): ?><label class="checkbox-label"><input type="checkbox" name="use_audio_files[]" value="<?=h($f)?>" <?=!empty($editData['video_audio'][$f])?'checked':''?>> <span>Use video file audio</span></label><?php endif; ?><?php else: ?><small>No caption for audio files</small><?php endif; ?></div><button type="button" onclick="deleteFile(<?=h(json_encode($f))?>)">Remove</button></div><?php endforeach; ?></div><button type="submit">Save changes</button></form><form method="post" id="delete-file-form" style="display:none"><input type="hidden" name="action" value="delete_file"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="file" id="delete-file-name" value=""></form><form method="post" onsubmit="return confirm('Delete this input directory?')"><input type="hidden" name="action" value="delete_dir"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><label class="checkbox-label"><input type="checkbox" name="delete_output" value="1"> <span>Also delete generated video, if any</span></label><button class="danger" type="submit">Delete input dir</button></form></section><?php endif; ?>
<?php if($tab==='new'): ?><section><h2>Add new movie input</h2><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="create"><label>Title*<input name="title" required></label><label>Date<input name="date" placeholder="2026-05-25"></label><label>Place<input name="place"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-new"></textarea><small id="description-counter-new" class="counter"></small></label><label>Images / videos*<input type="file" name="media[]" multiple required accept="image/*,video/*"></label><label>Audio (optional)<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Create input-dir</button></form></section><?php endif; ?>

File diff suppressed because one or more lines are too long