This gist contains all files use in this jsfiddle that shows information from DockerHub for a given project group on Gitlab.
The result can be seen here:
https://jsfiddle.net/gh/gist/library/pure/644812477bb8f432cb75256adc1a1a75
This gist contains all files use in this jsfiddle that shows information from DockerHub for a given project group on Gitlab.
The result can be seen here:
https://jsfiddle.net/gh/gist/library/pure/644812477bb8f432cb75256adc1a1a75
.component { | |
background-color: white; | |
margin: 0.5em; | |
} | |
.component-item[data-label]::before { | |
content: attr(data-label) ":"; | |
} | |
.component .component-item.tags { | |
margin-bottom: 0; | |
} | |
.component-item[data-color="blue"] .value, | |
.component-item[data-color="#066da5"] .value { | |
background-color: #066da5; | |
color: white; | |
} | |
.component-item[data-color="brightgreen"] .value { | |
background-color: green; | |
color: white; | |
} | |
.component-item[data-color="lightgrey"] .value { | |
background-color: goldenrod; | |
color: white; | |
} | |
.component-item[data-color="red"] .value { | |
background-color: crimson; | |
color: white; | |
} | |
.sorted-by-docker_size .tags:not(.docker_size), | |
.sorted-by-docker_layers .tags:not(.docker_layers), | |
.sorted-by-docker_pulls .tags:not(.docker_pulls) { | |
opacity: 0.35; | |
} | |
.component.not-found, | |
.component-item.docker_stars { display: none; } |
<section class="section"> | |
<nav class="level"> | |
<div class="level-left"> | |
<div class="level-item"> | |
<h1 class="title"> | |
<span class="subtitle">Pipeline-components</span> | |
<span>overview</span> | |
</h1> | |
</div> | |
</div> | |
<div class="level-right"> | |
<div class="level-item buttons has-addons JS-sort-by-button-group"> | |
<button class="button is-selected is-link" data-sort-by="name"> | |
<span class="icon"><i class="far fa-heart"></i></span> | |
<span>Name</span> | |
</button> | |
<button class="button is-link is-outlined" data-sort-by="docker_pulls" data-sort-descending="true"> | |
<span class="icon"><i class="far fa-arrow-alt-circle-down"></i></span> | |
<span>Docker Pulls</span> | |
</button> | |
<button class="button is-link is-outlined" data-sort-by="docker_size"> | |
<span class="icon"><i class="far fa-file-alt"></i></span> | |
<span>Image Size</span> | |
</button> | |
<button class="button is-link is-outlined" data-sort-by="docker_layers"> | |
<span class="icon"><i class="fas fa-layer-group"></i></span> | |
<span>Image Layers</span> | |
</button> | |
</div> | |
</div> | |
</nav> | |
<div class="container is-fluid"> | |
<div class="component-list"></div> | |
</div> | |
</section> | |
<footer class="footer"> | |
<div class="content has-text-centered"> | |
<p> | |
<strong>Pipeline-components overview</strong> by <a href="https://pother.ca">Potherca</a>. | |
</p> | |
</div> | |
</footer> |
/*global fetch, Request, Isotope */ | |
/*/ User Data /*/ | |
const userData = { | |
docker_group: 'pipelinecomponents', | |
// gitlab_domain = 'gitlab.com', | |
gitlab_group: 'pipeline-components', | |
gitlab_group_id: 3749748, | |
} | |
/*/ Application data /*/ | |
const container = document.querySelector('.component-list') | |
// Make sure resources are cached to avoid HTTP 429 response codes | |
const requestConfig = {cache: 'force-cache'} | |
/*/ Application logic /*/ | |
const fetchJson = url => | |
fetch( new Request(url, requestConfig) ) | |
.then( response => response.ok | |
? response.json() | |
: new Error('Could not retrieve API response, status = ' + response.status) | |
) | |
// Fetch list of project names from the Gitlab API | |
const fetchProjectNames = ({ docker_group, gitlab_group, gitlab_group_id, gitlab_domain = 'gitlab.com' }) => | |
fetchJson(`https://${gitlab_domain}/api/v4/groups/${gitlab_group_id}/projects?per_page=100&simple=true`) | |
// Grab name from each project | |
.then( projects => projects.flatMap(project => project.name).filter(name => name) ) | |
// Remove blacklisted projects | |
.then( projects => projects.filter(project => ['global-issues','example','ci-config', 'org'].includes(project) === false) ) | |
// Build URLs for each project | |
.then( names => names.map( name => ({ | |
name : name, | |
gitlab_url : `https://${gitlab_domain}/${gitlab_group}/${name}`, | |
gitlab_build : `https://img.shields.io/gitlab/pipeline/${gitlab_group}/${name}.json?gitlab_url=https%3A%2F%2F${gitlab_domain}`, | |
docker_pulls : `https://img.shields.io/docker/pulls/${docker_group}/${name}.json`, | |
docker_size : `https://img.shields.io/microbadger/image-size/${docker_group}/${name}.json`, | |
docker_layers : `https://img.shields.io/microbadger/layers/${docker_group}/${name}.json`, | |
docker_stars : `https://img.shields.io/docker/stars/${docker_group}/${name}.json`, | |
}) | |
)) | |
// Grab information for all URLs | |
const fetchDockerData = collections => Promise.all( | |
Object.keys(collections[0]).map( | |
name => Promise.all( | |
collections.map( collection => | |
// Filter blacklisted values (those that do not need an AJAX call) | |
['name', 'gitlab_url'].includes(name) | |
? { name: collection.name, [name]: collection[name] } | |
: fetchJson( collection[name] ) | |
.then( data => ({ | |
name: collection.name, | |
[name]: data | |
})) | |
) | |
) | |
)) | |
// Combine all separate elements into one collection | |
.then(function (data) { | |
const combined = [] | |
data.forEach( (collections) => { | |
collections.forEach( (collection) => { | |
const name = collection.name | |
const index = combined.map( item => item.name ).indexOf( collection.name ); | |
if ( index === -1) { | |
combined.push(collection) | |
} else { | |
combined[index] = { ...combined[index],...collection} | |
} | |
}) | |
}) | |
return combined | |
}) | |
const attachHtml = collections => Promise.resolve(collections.map( collection => ` | |
<div class="box component ${collection.docker_pulls.value === 'repo not found'?'not-found':''}"> | |
<h2 class="title is-6 name has-text-centered"><a href="${collection.gitlab_url}">${collection.name}</a></h2> | |
${Object.keys(collection) | |
.filter( name => ! ['name', 'gitlab_url'].includes(name) ) | |
.map( name => ` | |
<p class="tags has-addons component-item ${name}" | |
data-color="${collection[name].color}" | |
> | |
<span class="tag">${collection[name].label}</span> | |
<span class="value tag">${collection[name].value}</span> | |
</p>`).join('') | |
} | |
</div> | |
` | |
)) | |
.then( elements => elements.forEach(html => { | |
container.insertAdjacentHTML( 'beforeend', html ) | |
})) | |
const addIsotope = async () => (container.isotope = new Isotope( container, { | |
itemSelector: '.component', | |
layoutMode: 'fitRows', | |
stagger: 15, | |
transitionDuration: 800, | |
sortBy : 'name', | |
getSortData: { | |
// This works for docker_size ONLY as long as all image have a size in MBs. | |
docker_size: itemElem => parseFloat( itemElem.querySelector('.docker_size .value').textContent ) || 999999999, | |
docker_layers: itemElem => parseInt( itemElem.querySelector('.docker_layers .value').textContent, 10 ) || 999999999, | |
name: '.name .value', // text from querySelector | |
docker_pulls: itemElem => | |
parseInt(itemElem.querySelector('.docker_pulls .value').textContent.replace( /k|M/g, (found) => { | |
if (found === 'k') {return '000'} | |
if (found === 'M') {return '000000'} | |
}), 10) || 0 | |
} | |
} | |
) | |
) | |
/*/ Application actions /*/ | |
fetchProjectNames(userData) | |
.then(fetchDockerData) | |
.then(attachHtml) | |
.then(addIsotope) | |
.then( () => { | |
/*/ Handle sorting buttons /*/ | |
const buttonsRoot = document.querySelector('.JS-sort-by-button-group') | |
buttonsRoot.addEventListener( 'click', event => { | |
const eventTarget = event.target.closest('button') | |
if (eventTarget) { | |
let classList = buttonsRoot.querySelector('.is-selected').classList | |
classList.remove('is-selected') | |
classList.add('is-outlined') | |
eventTarget.classList.add('is-selected') | |
eventTarget.classList.remove('is-outlined') | |
const sortItem = eventTarget.getAttribute('data-sort-by') | |
if (sortItem !== null) { | |
container.className = `component-lint sorted-by-${sortItem}` | |
container.isotope.arrange({ | |
sortBy: sortItem, | |
sortAscending: eventTarget.hasAttribute('data-sort-descending') | |
? false | |
: true | |
}) | |
} | |
} | |
}) | |
}) |
name: Gitlab group Dockerhub data | |
description: | |
authors: | |
- Potherca | |
resources: | |
- https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css | |
- https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css | |
- https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/3.0.6/isotope.pkgd.min.js | |
load_type: d | |
js_wrap: d |
- Add license and link to code source | |
- Add "please wait, fetching data" spinner | |
- Add filters for 50k+, 10k+, 10k<, etc. | |
- Add filter for which language/stack a component is for | |
- Add filter for passing/failing build | |
- Add text-search filter | |
- Add "Made by Potherca" style | |
- Create/Generate sorting/filter buttons from JS | |
- Create a version with a form so others can (re)use it | |
- Carefully align color pallet |