Files
docker-registry-ui/templates/catalog.html

216 lines
8.9 KiB
HTML

{{extends "base.html"}}
{{import "breadcrumb.html"}}
{{block head()}}
<script type="text/javascript" src="{{ basePath }}/static/js/bs5-confirmation.js"></script>
<script type="text/javascript" src="{{ basePath }}/static/js/sorting_natural.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var reposTable = $('#datatable_repos').DataTable({
"pageLength": 10,
"stateSave": true,
"dom": "<'row'<'col-sm-12'tr>><'row'<'col-sm-4'i><'col-sm-4 text-center'p><'col-sm-4 text-end'l>>",
"language": {
"emptyTable": "Catalog is being initializing...",
"info": "Showing _START_ to _END_ of _TOTAL_",
"infoFiltered": " (filtered from _MAX_)",
"infoEmpty": "Showing 0 entries"
}
});
var areTagsReady = {{ areTagsReady }};
var tagsTable = $('#datatable_tags').DataTable({
"pageLength": 10,
"order": [[ 0, 'desc' ]],
"stateSave": true,
"dom": "<'row'<'col-sm-12'tr>><'row'<'col-sm-4'i><'col-sm-4 text-center'p><'col-sm-4 text-end'l>>",
columnDefs: [
{ type: 'natural', targets: 0 }
],
"language": {
"emptyTable": areTagsReady ? "No tags." : "Tags are being loaded in the background...",
"info": "Showing _START_ to _END_ of _TOTAL_",
"infoFiltered": " (filtered from _MAX_)",
"infoEmpty": "Showing 0 entries"
}
});
// Restore search value from DataTables state
var savedSearch = reposTable.search();
if (!savedSearch) {
savedSearch = tagsTable.search();
}
if (savedSearch) {
$('#repos-search').val(savedSearch);
$('#clear-repos-search').show();
}
// Single search box that searches both tables
$('#repos-search').on('keyup', function() {
var searchValue = this.value;
reposTable.search(searchValue).draw();
tagsTable.search(searchValue).draw();
$('#clear-repos-search').toggle(searchValue.length > 0);
});
$('#clear-repos-search').on('click', function() {
$('#repos-search').val('');
reposTable.search('').draw();
tagsTable.search('').draw();
$(this).hide();
});
// Initialize Bootstrap tooltips
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function(el) {
new bootstrap.Tooltip(el);
});
function populateConfirmation() {
$('[data-bs-toggle=confirmation]').confirmationPopover({
title: 'Delete this tag?',
btnOkText: 'Delete',
btnCancelText: 'Cancel',
btnOkClass: 'btn-sm btn-danger',
btnCancelClass: 'btn-sm btn-secondary'
});
}
populateConfirmation()
$('#datatable_tags').on('draw.dt', populateConfirmation)
});
</script>
{{end}}
{{block body()}}
<div class="d-flex gap-3 mb-3">
<nav aria-label="breadcrumb" class="flex-grow-1">
<ol class="breadcrumb rounded shadow-sm mb-0 h-100 d-flex align-items-center">
{{ yield breadcrumb() repoPath }}
</ol>
</nav>
<div class="position-relative" style="width: 300px;">
<input type="text" id="repos-search" class="form-control" placeholder="Search...">
<button type="button" class="btn btn-link position-absolute top-50 end-0 translate-middle-y" id="clear-repos-search" style="display: none;">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
{{if len(repos)>0 || !isCatalogReady}}
<div class="card shadow-sm mb-4">
<div class="card-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
<h5 class="mb-0"><i class="bi bi-folder2 me-2"></i>Repositories</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table id="datatable_repos" class="table table-hover table-striped mb-0">
<thead class="table-light">
<tr>
<th>Repository</th>
<th width="20%">Tags</th>
</tr>
</thead>
<tbody>
{{range _, repo := repos}}
{{ full_repo_path := repoPath != "" ? repoPath+"/"+repo : repo }}
{{if !isset(tagCounts[full_repo_path]) || (isset(tagCounts[full_repo_path]) && tagCounts[full_repo_path] > 0)}}
<tr>
<td>
<i class="bi bi-folder2 text-primary me-2"></i>
<a href="{{ basePath }}/{{ full_repo_path }}" class="text-decoration-none fw-semibold">{{ repo }}</a>
</td>
<td><span class="badge bg-secondary">{{ tagCounts[full_repo_path] }}</span></td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{end}} {* end repos *}
{{if showTags}}
<div class="card shadow-sm mb-4">
<div class="card-header" style="background: linear-gradient(135deg, #65a30d 0%, #4d7c0f 100%); color: white;">
<h5 class="mb-0"><i class="bi bi-tags me-2"></i>Tags
{{if tagsRefreshInfo != ""}}
<i class="bi bi-info-circle ms-2" style="font-size: 0.8rem; opacity: 0.8;" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="{{ tagsRefreshInfo }}"></i>
{{end}}
</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table id="datatable_tags" class="table table-hover table-striped mb-0">
<thead class="table-light">
<tr>
<th>Tag Name</th>
</tr>
</thead>
<tbody>
{{range _, tag := tags}}
<tr>
<td>
<div class="d-flex justify-content-between align-items-center">
<div>
<i class="bi bi-tag text-success me-2"></i>
<a href="{{ basePath }}/{{ repoPath }}:{{ tag }}" class="text-decoration-none fw-semibold">{{ tag }}</a>
</div>
{{if deleteAllowed}}
<a href="{{ basePath }}/__delete-tag?repoPath={{ repoPath }}&tag={{ tag }}"
data-bs-toggle="confirmation"
class="btn btn-outline-danger btn-sm"
role="button">
<i class="bi bi-trash me-1"></i>Delete
</a>
{{end}}
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{end}} {* end tags *}
{{if eventsAllowed and isset(events) }}
<div class="card shadow-sm mb-4">
<div class="card-header" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white;">
<h5 class="mb-0"><i class="bi bi-clock-history me-2"></i>Recent Activity</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table id="datatable_events" class="table table-hover table-striped mb-0">
<thead class="table-light">
<tr>
<th>Action</th>
<th>Image</th>
<th>IP Address</th>
<th>User</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{{range _, e := events}}
<tr>
<td><span class="badge bg-primary">{{ e.Action }}</span></td>
{{if hasPrefix(e.Tag,"sha256:") }}
<td title="{{ e.Tag }}"><span class="small">{{ e.Repository }}@{{ e.Tag[:19] }}...</span></td>
{{else}}
<td><span class="small">{{ e.Repository }}:{{ e.Tag }}</span></td>
{{end}}
<td><span class="text-muted small">{{ e.IP }}</span></td>
<td><span class="text-muted small">{{ e.User }}</span></td>
<td><span class="text-muted small">{{ e.Created|pretty_time }}</span></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
{{end}}