- 134-tool registry with programmatic SEO (unique titles/H1/descriptions, JSON-LD graphs, sitemap index, canonical 301 enforcement via required filter) - DB-backed job queue (SKIP LOCKED) with drivers: Ffmpeg, Images (GD), Pdf (qpdf/gs/poppler), Youtube (thumbnails), Qr (server-side PNG) - Security: SSRF guard, MIME validation, rate limits, API-key auth, bcrypt admin login, security headers - Admin panel: dashboard, tools/categories/guides CRUD, SEO audit, analytics, job inspector with retry, system health, feature flags - Docker deployment (nginx + web/api FPM pools + scalable workers), PHPUnit suite (19 tests / 1139 assertions), PWA manifest + service worker
35 lines
3.3 KiB
PHP
35 lines
3.3 KiB
PHP
<?= view('partials/admin_nav') ?>
|
|
<h1 style="font-size:1.7rem;margin-bottom:1rem"><?= $tool ? 'Edit tool' : 'New tool' ?></h1>
|
|
<?php if ($e = session()->getFlashdata('error')): ?><p class="notice error"><?= esc($e) ?></p><?php endif ?>
|
|
<form method="post" action="/admin/tools<?= $tool ? '/' . (int)$tool['id'] : '' ?>" class="tool-panel" style="max-width:760px">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="_method" value="">
|
|
<div class="opt-grid" style="padding-top:0">
|
|
<div><label>Name *</label><input type="text" name="name" required value="<?= esc($tool['name'] ?? '') ?>"></div>
|
|
<div><label>Slug *</label><input type="text" name="slug" required pattern="[a-z0-9-]+" value="<?= esc($tool['slug'] ?? '') ?>"></div>
|
|
<div><label>Category</label>
|
|
<select name="category_id"><?php foreach ($categories as $c): ?><option value="<?= (int)$c['id'] ?>" <?= ($tool && (int)$tool['category_id']===(int)$c['id'])?'selected':'' ?>><?= esc($c['name']) ?></option><?php endforeach ?></select></div>
|
|
<div><label>Kind</label>
|
|
<select name="kind"><?php foreach (['upload','url','text'] as $k): ?><option value="<?= $k ?>" <?= ($tool['kind'] ?? '')===$k?'selected':'' ?>><?= $k ?></option><?php endforeach ?></select></div>
|
|
<div><label>Operation</label><input type="text" name="operation" value="<?= esc($tool['operation'] ?? 'convert') ?>" placeholder="convert / compress / trim …"></div>
|
|
<div><label>Primary format in</label><input type="text" name="format_in" value="" placeholder="mp4"></div>
|
|
<div><label>Primary format out</label><input type="text" name="format_out" value="" placeholder="mp3"></div>
|
|
<div><label>Status</label>
|
|
<select name="status"><?php foreach (['active','draft','inactive'] as $st): ?><option value="<?= $st ?>" <?= ($tool['status'] ?? 'active')===$st?'selected':'' ?>><?= $st ?></option><?php endforeach ?></select></div>
|
|
</div>
|
|
<div style="margin-top:1rem"><label>Short description</label>
|
|
<textarea name="short_description" class="tool-area" style="min-height:60px"><?= esc($tool['short_description'] ?? '') ?></textarea></div>
|
|
<div class="opt-grid" style="margin-top:1rem">
|
|
<div><label>SEO title</label><input type="text" name="seo_title" value="<?= esc($tool['seo_title'] ?? '') ?>"></div>
|
|
<div><label>H1</label><input type="text" name="h1" value="<?= esc($tool['h1'] ?? '') ?>"></div>
|
|
<div><label>Aliases (comma separated)</label><input type="text" name="aliases" value=""></div>
|
|
<div><label>Popularity</label><input type="number" name="popularity" value="<?= (int)($tool['popularity'] ?? 1000) ?>"></div>
|
|
</div>
|
|
<div style="margin-top:1rem"><label>Meta description</label>
|
|
<textarea name="seo_description" class="tool-area" style="min-height:60px"><?= esc($tool['seo_description'] ?? '') ?></textarea></div>
|
|
<div style="margin-top:1rem"><label>Intro paragraph</label>
|
|
<textarea name="intro" class="tool-area" style="min-height:80px"><?= esc($tool['intro'] ?? '') ?></textarea></div>
|
|
<p class="hint" style="margin-top:.8rem">Leave format fields blank for non-converter tools — SEO copy is auto-generated from the format pair where possible. Critical SEO checks block publishing.</p>
|
|
<button class="btn-primary" type="submit" style="margin-top:1rem"><?= $tool ? 'Save changes' : 'Create tool' ?></button>
|
|
</form>
|