Support multiple deploy template list URLs with cross-list dedupe
CI / test (push) Successful in 9m55s
CI / test (push) Successful in 9m55s
Add Settings UI to configure many Portainer-format template catalogs (not just one), merge them on fetch, drop duplicates by image/stack/title, and show source counts on the Deploy view.
This commit is contained in:
@@ -22,6 +22,11 @@ import notificationManager from './libs/notifications.js';
|
||||
import { initOpsApp } from './ui/ops-app.js';
|
||||
import { presentError, formatResponseError, isBackgroundMethod } from './client/errors.js';
|
||||
import { warmSnapshot } from './client/snapshot.js';
|
||||
import {
|
||||
fetchMergedTemplates,
|
||||
getTemplateListUrls,
|
||||
clearMergedTemplateCache,
|
||||
} from './client/templateLists.js';
|
||||
import {
|
||||
getTerminalCtor,
|
||||
getFitAddonCtor,
|
||||
@@ -4324,9 +4329,57 @@ async function bulkRemoveImages() {
|
||||
|
||||
// Deploy View Functions
|
||||
let deployViewTemplates = [];
|
||||
/** @type {{ sources?: number, unique?: number, duplicates?: number, fetched?: number }|null} */
|
||||
let deployViewTemplateStats = null;
|
||||
let deployTemplateSearchSetup = false;
|
||||
const DEPLOY_SEARCH_DEBOUNCE_MS = 280;
|
||||
|
||||
function clearDeployTemplateCache() {
|
||||
deployViewTemplates = [];
|
||||
deployViewTemplateStats = null;
|
||||
clearMergedTemplateCache();
|
||||
}
|
||||
window.__peardockClearDeployTemplateCache = clearDeployTemplateCache;
|
||||
|
||||
/**
|
||||
* @param {object[]} templates
|
||||
* @param {object} [stats]
|
||||
*/
|
||||
function setDeployTemplates(templates, stats) {
|
||||
deployViewTemplates = Array.isArray(templates) ? templates : [];
|
||||
deployViewTemplateStats = stats || null;
|
||||
// Keep modal catalog in sync if open path uses fetchTemplates separately
|
||||
try {
|
||||
fetchTemplates();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (typeof currentView !== 'undefined' && currentView === 'deploy') {
|
||||
const searchInput = document.getElementById('deploy-template-search-input');
|
||||
const query = searchInput?.value || '';
|
||||
displayDeployTemplateList(filterTemplatesByQuery(deployViewTemplates, query));
|
||||
updateDeployTemplateCount(query);
|
||||
}
|
||||
}
|
||||
window.__peardockSetDeployTemplates = setDeployTemplates;
|
||||
|
||||
function updateDeployTemplateCount(query = '') {
|
||||
const countEl = document.getElementById('deploy-template-count');
|
||||
if (!countEl) return;
|
||||
const total = deployViewTemplates.length;
|
||||
const q = String(query || '').trim();
|
||||
const filtered = q ? filterTemplatesByQuery(deployViewTemplates, q).length : total;
|
||||
const st = deployViewTemplateStats;
|
||||
let base = q ? `${filtered} of ${total} templates` : `${total} templates`;
|
||||
if (st?.sources != null) {
|
||||
base += ` · ${st.sources} list${st.sources === 1 ? '' : 's'}`;
|
||||
}
|
||||
if (st?.duplicates) {
|
||||
base += ` · ${st.duplicates} dupes removed`;
|
||||
}
|
||||
countEl.textContent = base;
|
||||
}
|
||||
|
||||
function setupDeployTemplateSearch() {
|
||||
const searchInput = document.getElementById('deploy-template-search-input');
|
||||
if (!searchInput || deployTemplateSearchSetup) return;
|
||||
@@ -4336,13 +4389,7 @@ function setupDeployTemplateSearch() {
|
||||
const query = searchInput.value;
|
||||
const filtered = filterTemplatesByQuery(deployViewTemplates, query);
|
||||
displayDeployTemplateList(filtered);
|
||||
const countEl = document.getElementById('deploy-template-count');
|
||||
if (countEl) {
|
||||
const q = query.trim();
|
||||
countEl.textContent = q
|
||||
? `${filtered.length} of ${deployViewTemplates.length} templates`
|
||||
: `${deployViewTemplates.length} templates`;
|
||||
}
|
||||
updateDeployTemplateCount(query);
|
||||
}, DEPLOY_SEARCH_DEBOUNCE_MS);
|
||||
|
||||
searchInput.addEventListener('input', runFilter);
|
||||
@@ -4386,19 +4433,17 @@ async function loadDeployView() {
|
||||
|
||||
// Show loading state only when we don't have cache
|
||||
if (!deployViewTemplates.length) {
|
||||
const n = getTemplateListUrls().length;
|
||||
templateListContainer.innerHTML =
|
||||
'<div class="text-center text-muted py-4"><i class="fas fa-spinner fa-spin me-2"></i>Loading templates...</div>';
|
||||
`<div class="text-center text-muted py-4"><i class="fas fa-spinner fa-spin me-2"></i>Loading templates from ${n} list${n === 1 ? '' : 's'}…</div>`;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!deployViewTemplates.length) {
|
||||
const response = await fetch(
|
||||
'https://raw.githubusercontent.com/Lissy93/portainer-templates/main/templates.json'
|
||||
);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
const data = await response.json();
|
||||
deployViewTemplates = Array.isArray(data.templates) ? data.templates : [];
|
||||
// Also keep modal list in sync when possible
|
||||
const result = await fetchMergedTemplates(getTemplateListUrls());
|
||||
deployViewTemplates = result.templates || [];
|
||||
deployViewTemplateStats = result.stats || null;
|
||||
// Sync modal list from the same merged set (avoid second multi-fetch when possible)
|
||||
try {
|
||||
await fetchTemplates();
|
||||
} catch {
|
||||
@@ -4408,12 +4453,11 @@ async function loadDeployView() {
|
||||
const searchInput = document.getElementById('deploy-template-search-input');
|
||||
const query = searchInput?.value || '';
|
||||
displayDeployTemplateList(filterTemplatesByQuery(deployViewTemplates, query));
|
||||
const countEl = document.getElementById('deploy-template-count');
|
||||
if (countEl) countEl.textContent = `${deployViewTemplates.length} templates`;
|
||||
updateDeployTemplateCount(query);
|
||||
} catch (error) {
|
||||
console.error('[ERROR] Failed to fetch templates:', error.message);
|
||||
templateListContainer.innerHTML =
|
||||
'<div class="text-center text-danger py-4"><i class="fas fa-exclamation-triangle me-2"></i>Failed to load templates. Check network access.</div>';
|
||||
'<div class="text-center text-danger py-4"><i class="fas fa-exclamation-triangle me-2"></i>Failed to load templates. Check network access and Settings → Deploy template lists.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user