| Current Path : /home/seto/indexator.pm/templates/projects/ |
| Current File : /home/seto/indexator.pm/templates/projects/index.php |
<?php
$title = 'Projects — Indexator';
ob_start();
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">Projects</h1>
<div class="d-flex gap-2">
<select id="project-sort" class="form-select" style="width:190px;">
<option value="name_asc">Name A → Z</option>
<option value="name_desc">Name Z → A</option>
<option value="total_desc">Most URLs</option>
<option value="rate_asc">Worst Index</option>
<option value="submitted_desc">Recently Submitted</option>
<option value="created_desc">Recently Added</option>
</select>
<input type="search" id="project-search" class="form-control"
placeholder="Search by name or domain…" style="width:220px;" autocomplete="off">
<?php $hasChecking = !empty(array_filter($projects, fn($p) => $p['status'] === 'checking')); ?>
<?php $hasNotIndexed = !empty(array_filter($projects, fn($p) => (int)$p['not_indexed'] > 0)); ?>
<?php if ($hasNotIndexed && !$hasChecking): ?>
<button id="recheck-all-btn" class="btn btn-outline-primary"
data-csrf="<?= htmlspecialchars(\App\Core\CsrfGuard::getToken()) ?>">
<i class="bi bi-arrow-clockwise"></i> Recheck All
</button>
<?php endif; ?>
<?php if ($hasChecking): ?>
<button id="stop-all-btn" class="btn btn-danger"
data-csrf="<?= htmlspecialchars(\App\Core\CsrfGuard::getToken()) ?>">
■ Stop All
</button>
<?php endif; ?>
<a href="/export/all?filter=not_indexed" class="btn btn-outline-secondary">↓ Export Not Indexed</a>
<a href="/projects/create" class="btn btn-dark">+ New Project</a>
</div>
</div>
<?php if (!empty($projects)): ?>
<!-- ── Overall summary card ─────────────────────────────────────────────── -->
<div id="summary-card" class="card shadow-sm mb-4 border-0 bg-dark text-white">
<div class="card-body">
<div class="row g-3 align-items-center">
<div class="col-sm-auto">
<div class="fw-semibold opacity-75 small mb-1">All projects <span class="opacity-50">(<?= count($projects) ?>)</span></div>
<div class="d-flex gap-3 flex-wrap">
<div>
<span class="opacity-75 small">Total URLs</span><br>
<span class="fs-5 fw-bold"><?= number_format($summary['total']) ?></span>
</div>
<div>
<span class="opacity-75 small">Checked</span><br>
<span class="fs-5 fw-bold"><?= number_format($summary['checked']) ?></span>
</div>
<div>
<span class="text-success small">Indexed</span><br>
<span class="fs-5 fw-bold text-success"><?= number_format($summary['indexed']) ?></span>
<?php if ($summary['delta_indexed'] !== null && $summary['delta_indexed'] !== 0): ?>
<?php $sd = $summary['delta_indexed']; ?>
<span class="small <?= $sd > 0 ? 'text-success' : 'text-danger' ?>">
<?= $sd > 0 ? '▲ +' : '▼ ' ?><?= $sd ?>
</span>
<?php endif; ?>
</div>
<div>
<span class="text-warning small">Not indexed</span><br>
<span class="fs-5 fw-bold text-warning"><?= number_format($summary['not_indexed']) ?></span>
</div>
<?php if ($summary['error'] > 0): ?>
<div>
<span class="text-danger small">Error</span><br>
<span class="fs-5 fw-bold text-danger"><?= number_format($summary['error']) ?></span>
</div>
<?php endif; ?>
</div>
</div>
<div class="col">
<div class="mb-2">
<div class="d-flex justify-content-between small opacity-75 mb-1">
<span>Check progress</span>
<span><?= $summary['check_pct'] ?>%</span>
</div>
<div class="progress" style="height:8px;">
<div class="progress-bar bg-light"
style="width:<?= $summary['check_pct'] ?>%"></div>
</div>
</div>
<div>
<div class="d-flex justify-content-between small opacity-75 mb-1">
<span>Indexation rate</span>
<span><?= $summary['indexation_rate'] ?>%</span>
</div>
<div class="progress" style="height:8px;">
<div class="progress-bar bg-success"
style="width:<?= $summary['indexation_rate'] ?>%"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if (empty($projects)): ?>
<div class="text-center text-muted py-5">
<p class="mb-3">No projects yet.</p>
<a href="/projects/create" class="btn btn-dark">Create your first project</a>
</div>
<?php else: ?>
<div class="row g-3" id="projects-list">
<?php foreach ($projects as $p): ?>
<?php
$total = (int)$p['total'];
$checked = (int)$p['checked'];
$indexed = (int)$p['indexed'];
?>
<?php
$borderColor = match($p['status']) {
'checking' => '#ffc107',
'done' => '#198754',
default => '#adb5bd',
};
$badgeClass = match($p['status']) {
'checking' => 'bg-warning text-dark',
'done' => 'bg-success',
default => 'bg-secondary',
};
?>
<div class="col-12 project-card"
data-name="<?= htmlspecialchars(mb_strtolower($p['name'])) ?>"
data-domain="<?= htmlspecialchars(mb_strtolower($p['domain'])) ?>"
data-total="<?= (int)$p['total'] ?>"
data-rate="<?= (int)$p['indexation_rate'] ?>"
data-submitted="<?= $p['last_indexed_at'] ? strtotime($p['last_indexed_at']) : 0 ?>"
data-created="<?= strtotime($p['created_at']) ?>"
data-last-checked="<?= $p['updated_at'] ? strtotime($p['updated_at']) : 0 ?>">
<div class="card project-card-inner" style="border-left: 4px solid <?= $borderColor ?>;">
<div class="card-body py-3">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-2">
<div class="min-w-0">
<a href="/projects/<?= $p['id'] ?>" class="fw-semibold text-decoration-none text-dark project-title">
<?= htmlspecialchars($p['name']) ?>
</a>
<span class="text-muted small ms-2"><?= htmlspecialchars($p['domain']) ?></span>
</div>
<div class="d-flex align-items-center gap-2 ms-3 flex-shrink-0">
<span class="badge <?= $badgeClass ?>"><?= htmlspecialchars($p['status']) ?></span>
<?php if ($p['last_indexed_at']): ?>
<?php
$idxDiff = round((time() - strtotime($p['last_indexed_at'])) / 86400);
$idxLabel = $idxDiff === 0 ? 'today' : $idxDiff . 'd ago';
$idxTip = htmlspecialchars(
number_format((int)$p['last_indexed_count']) . ' URLs · '
. $idxLabel . ' · ' . strtoupper($p['last_indexed_engines'])
. ' · ' . $p['last_indexed_status']
);
?>
<span class="badge-indexed-dot" data-bs-toggle="tooltip"
data-bs-placement="top" title="<?= $idxTip ?>"><i class="bi bi-lightning-charge-fill"></i></span>
<?php endif; ?>
<?php
$recentlyChecked = $p['updated_at'] && (time() - strtotime($p['updated_at'])) < 6 * 3600;
$recentlySubmitted = $p['last_indexed_at'] && (time() - strtotime($p['last_indexed_at'])) < 72 * 3600;
$checkedAgo = $p['updated_at'] ? round((time() - strtotime($p['updated_at'])) / 3600, 1) . 'h ago' : '';
$submittedAgo = $p['last_indexed_at'] ? round((time() - strtotime($p['last_indexed_at'])) / 3600, 1) . 'h ago' : '';
?>
<?php if ((int)$p['not_indexed'] > 0 && $p['status'] !== 'checking'): ?>
<button class="btn-recheck-project btn-project-arrow<?= $recentlyChecked ? ' btn-recently-used' : '' ?>"
data-project-id="<?= $p['id'] ?>"
title="Recheck not indexed<?= $recentlyChecked ? ' · last checked ' . $checkedAgo : '' ?>"><i class="bi bi-arrow-clockwise"></i></button>
<?php endif; ?>
<button class="btn-index-project btn-project-arrow<?= $recentlySubmitted ? ' btn-recently-used' : '' ?>"
data-project-id="<?= $p['id'] ?>"
data-project-name="<?= htmlspecialchars($p['name']) ?>"
data-not-indexed="<?= (int)$p['not_indexed'] ?>"
title="Submit to indexing<?= $recentlySubmitted ? ' · submitted ' . $submittedAgo : '' ?>"><i class="bi bi-send"></i></button>
<a href="/projects/<?= $p['id'] ?>" class="btn-project-arrow" title="Open project"><i class="bi bi-chevron-right"></i></a>
</div>
</div>
<!-- Stats + indexation rate -->
<div class="d-flex align-items-center justify-content-between gap-3 mb-2 flex-wrap">
<div class="project-stats-row">
<span class="stat-item"><span class="stat-label">Total</span> <span class="stat-val"><?= number_format($total) ?></span></span>
<span class="stat-sep">·</span>
<span class="stat-item"><span class="stat-label">Checked</span> <span class="stat-val"><?= number_format($checked) ?></span></span>
<span class="stat-sep">·</span>
<span class="stat-item"><span class="stat-label">Indexed</span> <span class="stat-val text-success fw-semibold"><?= number_format($indexed) ?></span></span>
<span class="stat-sep">·</span>
<span class="stat-item"><span class="stat-label">Not indexed</span> <span class="stat-val text-warning-emphasis"><?= number_format((int)$p['not_indexed']) ?></span></span>
<?php if ((int)$p['error'] > 0): ?>
<span class="stat-sep">·</span>
<span class="stat-item"><span class="stat-label">Errors</span> <span class="stat-val text-danger"><?= (int)$p['error'] ?></span></span>
<?php endif; ?>
</div>
<div class="d-flex align-items-baseline gap-2 flex-shrink-0">
<span class="indexation-rate-big" style="color:<?= $borderColor ?>;"><?= $p['indexation_rate'] ?>%</span>
<?php if ($p['delta_indexed'] !== null && $p['delta_indexed'] !== 0): ?>
<?php $di = (int)$p['delta_indexed']; ?>
<span class="small <?= $di > 0 ? 'text-success' : 'text-danger' ?>">
<?= $di > 0 ? '▲ +' . $di : '▼ ' . $di ?>
</span>
<?php endif; ?>
</div>
</div>
<!-- Progress bars -->
<div class="d-flex gap-2 flex-column">
<div>
<div class="d-flex justify-content-between" style="font-size:0.7rem; color:#adb5bd; margin-bottom:3px;">
<span>Check progress</span><span><?= $p['check_pct'] ?>%</span>
</div>
<div class="progress" style="height:4px; border-radius:2px;">
<div class="progress-bar bg-secondary" style="width:<?= $p['check_pct'] ?>%"></div>
</div>
</div>
<div>
<div class="d-flex justify-content-between" style="font-size:0.7rem; color:#adb5bd; margin-bottom:3px;">
<span>Indexation</span><span><?= $indexed ?> / <?= $checked ?></span>
</div>
<div class="progress" style="height:4px; border-radius:2px;">
<div class="progress-bar" style="width:<?= $p['indexation_rate'] ?>%; background:<?= $borderColor ?>;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div id="no-search-results" class="text-center text-muted py-5" style="display:none;">
No projects match your search.
</div>
<?php endif; ?>
<?php
$content = ob_get_clean();
require __DIR__ . '/../layout.php';