Skip to content

Instantly share code, notes, and snippets.

@wojtekmaj
Last active October 19, 2022 12:42
Show Gist options
  • Save wojtekmaj/a808b49a0cf77ebf6944ec3b429bc446 to your computer and use it in GitHub Desktop.
Save wojtekmaj/a808b49a0cf77ebf6944ec3b429bc446 to your computer and use it in GitHub Desktop.
GitHub Actions Dashboard
<!DOCTYPE html>
<html>
<title>GitHub Actions dashboard</title>
<style>
:root {
font-size: 14px;
--background-color: rgb(246 248 250);
--canvas-background-color: rgb(255 255 255);
--canvas-border-color: rgb(216 222 228);
--canvas-shadow: rgba(140 149 159 / 15%) 0 3px 6px 0;
--text-color: rgb(0 0 0);
--text-color-muted: rgb(87 96 106);
--primary-color: rgb(9 105 218);
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: rgb(1 4 9);
--canvas-background-color: rgb(13 17 23);
--canvas-border-color: rgb(33 38 45);
--canvas-shadow: rgb(1 4 9) 0 3px 6px 0;
--text-color: rgb(255 255 255);
--text-color-muted: rgb(139 148 158);
--primary-color: rgb(88 166 255);
}
}
body {
margin: 24px;
background: var(--background-color);
color: var(--text-color);
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
line-height: 1.5;
}
a {
color: inherit;
text-decoration: none;
}
a:hover {
color: var(--primary-color);
text-decoration: underline;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
}
h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover {
text-decoration: none;
}
gh-repos {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 1rem;
margin: 1rem 0;
}
gh-repo {
padding: 1rem;
background: var(--canvas-background-color);
border: 1px solid var(--canvas-border-color);
border-radius: 4px;
box-shadow: var(--canvas-shadow);
}
gh-repo[archived="true"] {
opacity: 0.5;
}
gh-repo h3 {
margin-top: 0;
font-size: 1rem;
}
p[data-status] {
margin-bottom: 0;
color: var(--text-color-muted);
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
</style>
</html>
<body>
<div id="root">
<h1>GitHub Actions dashboard</h1>
</div>
<script>
const githubToken = 'GITHUB_TOKEN_HERE';
const apiUrl = 'https://api.github.com';
const filteredCiNames = ['Close stale issues'];
const alwaysAllowedRepoNames = ['wojtekmaj/react-qr-svg'];
function fetchJSON(url) {
const headers = {};
if (githubToken) {
headers['authorization'] = `Bearer ${githubToken}`;
}
return fetch(url, { headers }).then(response => response.json());
}
class GitHubActions extends HTMLElement {
static get observedAttributes() {
return ['full_name'];
}
fetchData() {
const fullName = this.getAttribute('full_name');
const url = `${apiUrl}/repos/${fullName}/actions/workflows`;
fetchJSON(url)
.then(data => {
const { workflows } = data;
if (workflows.length === 0) {
this.status.textContent = 'No workflows found.';
return;
}
// Hide status
this.status.setAttribute('hidden', '');
const list = document.createElement('ul');
workflows
.filter(workflow => !filteredCiNames.includes(workflow.name))
.forEach(workflow => {
const item = document.createElement('li');
const link = document.createElement('a');
link.href = workflow.html_url;
const badge = document.createElement('img');
badge.src = workflow.badge_url;
badge.width = 90;
badge.height = 20;
badge.titleElement = workflow.name;
badge.onerror =() => {
const textNode = document.createTextNode(workflow.name);
badge.replaceWith(textNode);
};
link.appendChild(badge);
item.appendChild(link);
list.appendChild(item);
});
this.appendChild(list);
});
}
connectedCallback() {
this.init();
this.render();
}
attributeChangedCallback() {
if (!this.mounted) {
return;
}
this.init();
this.render();
this.fetchData();
}
init() {
this.mounted = true;
// Clear the element
this.innerHTML = '';
this.status = document.createElement('p');
this.status.setAttribute('data-status', '');
this.appendChild(this.status);
}
render() {
this.status.removeAttribute('hidden');
this.status.textContent = 'Loading…';
}
}
class GitHubRepo extends HTMLElement {
static get observedAttributes() {
return ['full_name', 'name', 'owner', 'owner_url', 'url'];
}
connectedCallback() {
this.init();
this.render();
}
attributeChangedCallback() {
if (!this.mounted) {
return;
}
this.render();
}
init() {
this.mounted = true;
// Clear the element
this.innerHTML = '';
this.titleElement = document.createElement('h3');
this.ownerLink = document.createElement('a');
this.titleElement.appendChild(this.ownerLink);
const separator = document.createElement('span');
separator.innerHTML = '&nbsp;/&nbsp;';
separator.style.fontWeight = 'normal';
this.titleElement.appendChild(separator);
this.repoLink = document.createElement('a');
this.titleElement.appendChild(this.repoLink);
this.appendChild(this.titleElement);
this.actions = document.createElement('gh-actions');
this.appendChild(this.actions);
}
render() {
this.ownerLink.href = this.getAttribute('owner_url');
this.ownerLink.textContent = this.getAttribute('owner');
this.repoLink.href = this.getAttribute('url');
this.repoLink.textContent = this.getAttribute('name');
this.actions.setAttribute('full_name', this.getAttribute('full_name'));
}
}
class GitHubRepos extends HTMLElement {
static get observedAttributes() {
return ['url'];
}
fetchData() {
const url = this.getAttribute('url');
if (!url) {
return;
}
fetchJSON(url)
.then(repos => {
// Hide status
this.status.setAttribute('hidden', '');
repos
.filter(repo => {
if (repo.fork && !alwaysAllowedRepoNames.includes(repo.full_name)) {
console.info(`Skipping ${repo.full_name} as it's a fork.`);
return false;
}
return true;
})
.forEach(repo => {
const ghRepo = document.createElement('gh-repo');
ghRepo.setAttribute('archived', repo.archived);
ghRepo.setAttribute('full_name', repo.full_name);
ghRepo.setAttribute('name', repo.name);
ghRepo.setAttribute('owner', repo.owner.login);
ghRepo.setAttribute('owner_url', repo.owner.html_url);
ghRepo.setAttribute('url', repo.html_url);
this.appendChild(ghRepo);
});
});
}
connectedCallback() {
this.init();
this.render();
}
attributeChangedCallback() {
if (!this.mounted) {
return;
}
this.init();
this.render();
this.fetchData();
}
init() {
this.mounted = true;
// Clear the element
this.innerHTML = '';
this.status = document.createElement('p');
this.status.setAttribute('data-status', '');
this.appendChild(this.status);
}
render() {
this.status.removeAttribute('hidden');
this.status.textContent = 'Loading…';
}
}
class GitHubUserRepos extends HTMLElement {
static get observedAttributes() {
return ['username'];
}
connectedCallback() {
this.init();
this.render();
}
attributeChangedCallback() {
if (!this.mounted) {
return;
}
this.render();
}
init() {
this.mounted = true;
// Clear the element
this.innerHTML = '';
this.titleElement = document.createElement('h2');
this.appendChild(this.titleElement);
this.repos = document.createElement('gh-repos');
this.appendChild(this.repos);
}
render() {
const username = this.getAttribute('username');
this.titleElement.textContent = `${username}’s repositories`;
const url = `${apiUrl}/users/${username}/repos?per_page=100`;
this.repos.setAttribute('url', url);
}
}
class GitHubOrgRepos extends HTMLElement {
static get observedAttributes() {
return ['org'];
}
connectedCallback() {
this.init();
this.render();
}
attributeChangedCallback() {
if (!this.mounted) {
return;
}
this.render();
}
init() {
this.mounted = true;
// Clear the element
this.innerHTML = '';
this.titleElement = document.createElement('h2');
this.appendChild(this.titleElement);
this.repos = document.createElement('gh-repos');
this.appendChild(this.repos);
}
render() {
const org = this.getAttribute('org');
this.titleElement.textContent = `Repositories in ${org}`;
const url = `${apiUrl}/orgs/${org}/repos?per_page=100`;
this.repos.setAttribute('url', url);
}
}
customElements.define('gh-actions', GitHubActions);
customElements.define('gh-repo', GitHubRepo);
customElements.define('gh-repos', GitHubRepos);
customElements.define('gh-user-repos', GitHubUserRepos);
customElements.define('gh-org-repos', GitHubOrgRepos);
const root = document.getElementById('root');
const ghRepos = document.createElement('gh-user-repos');
ghRepos.setAttribute('username', 'USERNAME_HERE');
root.appendChild(ghRepos);
const ghRepos2 = document.createElement('gh-org-repos');
ghRepos2.setAttribute('org', 'ORG_NAME_HERE');
root.appendChild(ghRepos2);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment