feat: repo list search, header sort, pagination (stage 6)
- list_repos: Pulp next 페이지 끝까지 순회 (첫-페이지-only 누락 수정) - BFF 메모리 검색(이름)·정렬(이름/검증/버전/패키지/동기화)·페이지네이션(20/페이지) - GET /repos/table 조각: 검색창은 표 밖(포커스 유지), 정렬헤더/페이저는 표 안, hx-include 로 검색어·정렬상태 상호 전송 - 폰트 재조정: 데이터 셀 14px, 헤더 13px, 배지/액션버튼 14px (동기화 버튼 대비 균형) - 버전 페이지 제목↔검증상세 마진, 데모 저장소 26개로 확장 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,43 +9,15 @@
|
||||
<div class="card"><div class="value">{{ summary.total_packages }}</div><div class="label">총 패키지</div></div>
|
||||
</div>
|
||||
|
||||
{% if repos %}
|
||||
<div class="table-card">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>저장소</th>
|
||||
<th>검증</th>
|
||||
<th class="num">최신 버전</th>
|
||||
<th class="num">패키지 수</th>
|
||||
<th>마지막 동기화</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for repo in repos %}
|
||||
<tr>
|
||||
<td class="repo-name"><a href="/repos/{{ repo.uuid }}/versions">{{ repo.name }}</a></td>
|
||||
<td><span class="badge badge-{{ repo.gpg.level }}">{{ repo.gpg.label }}</span></td>
|
||||
<td class="num">{% if repo.latest_version is not none %}v{{ repo.latest_version }}{% else %}—{% endif %}</td>
|
||||
<td class="num">{% if repo.package_count is not none %}{{ repo.package_count }}{% else %}—{% endif %}</td>
|
||||
<td class="mono">{{ repo.last_sync or "—" }}</td>
|
||||
<td>
|
||||
<div id="sync-{{ repo.uuid }}" class="row-action">
|
||||
<button class="secondary btn-sm"
|
||||
hx-post="/repos/{{ repo.uuid }}/sync"
|
||||
hx-target="#sync-{{ repo.uuid }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-disabled-elt="this">동기화</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="list-toolbar">
|
||||
<!-- 검색창은 표 바깥에 둬서 입력 중 포커스 유지. 정렬 상태([data-state])를 함께 전송 -->
|
||||
<input id="repo-search" class="repo-search" type="search" name="q" value="{{ q }}"
|
||||
placeholder="저장소 이름 검색"
|
||||
hx-get="/repos/table" hx-target="#repo-table-wrap" hx-swap="outerHTML"
|
||||
hx-trigger="input changed delay:300ms, search"
|
||||
hx-include="[data-state]">
|
||||
</div>
|
||||
{% else %}
|
||||
<p>등록된 저장소가 없습니다.</p>
|
||||
{% endif %}
|
||||
|
||||
{% include "partials/table_wrap.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
75
app/templates/partials/table_wrap.html
Normal file
75
app/templates/partials/table_wrap.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{#
|
||||
표 + 페이저 조각. 검색/정렬/페이지 이동의 swap 대상(#repo-table-wrap).
|
||||
정렬 헤더와 페이저는 현재 검색어(q)를 #repo-search 에서 hx-include 로 가져온다.
|
||||
hidden state(sort/dir, data-state)는 검색창이 정렬 상태를 함께 보내도록 노출.
|
||||
#}
|
||||
<div id="repo-table-wrap">
|
||||
<input type="hidden" name="sort" value="{{ sort }}" data-state>
|
||||
<input type="hidden" name="dir" value="{{ dir }}" data-state>
|
||||
|
||||
{% macro sorth(col, label, extra="") %}
|
||||
<th class="{{ extra }}">
|
||||
<a href="#" class="th-sort{% if sort == col %} is-active{% endif %}"
|
||||
hx-get="/repos/table" hx-target="#repo-table-wrap" hx-swap="outerHTML"
|
||||
hx-include="#repo-search"
|
||||
hx-vals='{"sort": "{{ col }}", "dir": "{{ 'desc' if (sort == col and dir == 'asc') else 'asc' }}"}'>
|
||||
{{ label }}{% if sort == col %} <span class="sort-ind">{{ '↑' if dir == 'asc' else '↓' }}</span>{% endif %}
|
||||
</a>
|
||||
</th>
|
||||
{% endmacro %}
|
||||
|
||||
{% if total %}
|
||||
<div class="table-card">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{{ sorth("name", "저장소") }}
|
||||
{{ sorth("verified", "검증") }}
|
||||
{{ sorth("version", "최신 버전", "num") }}
|
||||
{{ sorth("packages", "패키지 수", "num") }}
|
||||
{{ sorth("last_sync", "마지막 동기화") }}
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for repo in repos %}
|
||||
<tr>
|
||||
<td class="repo-name"><a href="/repos/{{ repo.uuid }}/versions">{{ repo.name }}</a></td>
|
||||
<td><span class="badge badge-{{ repo.gpg.level }}">{{ repo.gpg.label }}</span></td>
|
||||
<td class="num">{% if repo.latest_version is not none %}v{{ repo.latest_version }}{% else %}—{% endif %}</td>
|
||||
<td class="num">{% if repo.package_count is not none %}{{ repo.package_count }}{% else %}—{% endif %}</td>
|
||||
<td class="mono">{{ repo.last_sync or "—" }}</td>
|
||||
<td>
|
||||
<div id="sync-{{ repo.uuid }}" class="row-action">
|
||||
<button class="secondary btn-sm"
|
||||
hx-post="/repos/{{ repo.uuid }}/sync"
|
||||
hx-target="#sync-{{ repo.uuid }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-disabled-elt="this">동기화</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if pages > 1 %}
|
||||
<div class="pager">
|
||||
<button class="secondary btn-sm" {% if page <= 1 %}disabled{% endif %}
|
||||
hx-get="/repos/table" hx-target="#repo-table-wrap" hx-swap="outerHTML"
|
||||
hx-include="#repo-search"
|
||||
hx-vals='{"sort": "{{ sort }}", "dir": "{{ dir }}", "page": "{{ page - 1 }}"}'>이전</button>
|
||||
<span class="mono">{{ page }} / {{ pages }}</span>
|
||||
<button class="secondary btn-sm" {% if page >= pages %}disabled{% endif %}
|
||||
hx-get="/repos/table" hx-target="#repo-table-wrap" hx-swap="outerHTML"
|
||||
hx-include="#repo-search"
|
||||
hx-vals='{"sort": "{{ sort }}", "dir": "{{ dir }}", "page": "{{ page + 1 }}"}'>다음</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif q %}
|
||||
<p class="empty-note">'{{ q }}' 검색 결과가 없습니다.</p>
|
||||
{% else %}
|
||||
<p class="empty-note">등록된 저장소가 없습니다.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user