From 7beac85f252670729cc9be9cd52cce7166d5c075 Mon Sep 17 00:00:00 2001 From: Joxit Date: Sun, 7 Mar 2021 11:24:50 +0100 Subject: [PATCH] feat(riot-v5): export docker image class and utils function --- package.json | 1 + src/scripts/docker-image.js | 150 ++++++++++++++++++++++++++++++++++++ src/scripts/router.js | 5 +- src/scripts/utils.js | 85 ++++++++++++-------- 4 files changed, 210 insertions(+), 31 deletions(-) create mode 100644 src/scripts/docker-image.js diff --git a/package.json b/package.json index 3e7eb0c..64979ae 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@babel/core": "^7.12.9", "@babel/preset-env": "^7.12.7", "@riotjs/compiler": "^5.3.1", + "@riotjs/observable": "^4.0.4", "@riotjs/route": "^7.0.0", "@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-commonjs": "^17.0.0", diff --git a/src/scripts/docker-image.js b/src/scripts/docker-image.js new file mode 100644 index 0000000..684d354 --- /dev/null +++ b/src/scripts/docker-image.js @@ -0,0 +1,150 @@ +import { Http } from './http'; +import { isDigit, eventTransfer } from './utils'; +import observable from '@riotjs/observable'; + +const tagReduce = (acc, e) => { + if (acc.length > 0 && isDigit(acc[acc.length - 1].charAt(0)) == isDigit(e)) { + acc[acc.length - 1] += e; + } else { + acc.push(e); + } + return acc; +}; + +export function compare(e1, e2) { + const tag1 = e1.tag.match(/./g).reduce(tagReduce, []); + const tag2 = e2.tag.match(/./g).reduce(tagReduce, []); + + for (var i = 0; i < tag1.length && i < tag2.length; i++) { + const compare = tag1[i].localeCompare(tag2[i]); + if (isDigit(tag1[i].charAt(0)) && isDigit(tag2[i].charAt(0))) { + const diff = tag1[i] - tag2[i]; + if (diff != 0) { + return diff; + } + } else if (compare != 0) { + return compare; + } + } + return e1.tag.length - e2.tag.length; +} + +export class DockerImage { + constructor(name, tag, list) { + this.name = name; + this.tag = tag; + this.list = list; + this.chars = 0; + observable(this); + this.on('get-size', function () { + if (this.size !== undefined) { + return this.trigger('size', this.size); + } + return this.fillInfo(); + }); + this.on('get-sha256', function () { + if (this.size !== undefined) { + return this.trigger('sha256', this.sha256); + } + return this.fillInfo(); + }); + this.on('get-date', function () { + if (this.creationDate !== undefined) { + return this.trigger('creation-date', this.creationDate); + } + return this.fillInfo(); + }); + this.on('content-digest-chars', function (chars) { + this.chars = chars; + }); + this.on('get-content-digest-chars', function () { + return this.trigger('content-digest-chars', this.chars); + }); + this.on('get-content-digest', function () { + if (this.digest !== undefined) { + return this.trigger('content-digest', this.digest); + } + return this.fillInfo(); + }); + } + fillInfo() { + if (this._fillInfoWaiting) { + return; + } + this._fillInfoWaiting = true; + const oReq = new Http(); + const self = this; + oReq.addEventListener('loadend', function () { + if (this.status == 200 || this.status == 202) { + const response = JSON.parse(this.responseText); + if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json') { + self.trigger('list', response); + const manifest = response.manifests[0]; + const image = new DockerImage(self.name, manifest.digest); + eventTransfer(image, self); + image.fillInfo(); + self.variants = [image]; + return; + } + self.size = response.layers.reduce(function (acc, e) { + return acc + e.size; + }, 0); + self.sha256 = response.config.digest; + self.layers = response.layers; + self.trigger('size', self.size); + self.trigger('sha256', self.sha256); + oReq.getContentDigest(function (digest) { + self.digest = digest; + self.trigger('content-digest', digest); + if (!digest) { + // registryUI.showErrorCanNotReadContentDigest(); + } + }); + self.getBlobs(response.config.digest); + } else if (this.status == 404) { + // registryUI.errorSnackbar('Manifest for ' + self.name + ':' + self.tag + ' not found'); + } else { + // registryUI.snackbar(this.responseText); + } + }); + oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag); + oReq.setRequestHeader( + 'Accept', + 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json' + + (self.list ? ', application/vnd.docker.distribution.manifest.list.v2+json' : '') + ); + oReq.send(); + } + getBlobs() { + const oReq = new Http(); + const self = this; + oReq.addEventListener('loadend', function () { + if (this.status == 200 || this.status == 202) { + const response = JSON.parse(this.responseText); + self.creationDate = new Date(response.created); + self.blobs = response; + self.blobs.history + .filter(function (e) { + return !e.empty_layer; + }) + .forEach(function (e, i) { + e.size = self.layers[i].size; + e.id = self.layers[i].digest.replace('sha256:', ''); + }); + self.blobs.id = blob.replace('sha256:', ''); + self.trigger('creation-date', self.creationDate); + self.trigger('blobs', self.blobs); + } else if (this.status == 404) { + registryUI.errorSnackbar('Blobs for ' + self.name + ':' + self.tag + ' not found'); + } else { + registryUI.snackbar(this.responseText); + } + }); + oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/blobs/' + blob); + oReq.setRequestHeader( + 'Accept', + 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json' + ); + oReq.send(); + } +} diff --git a/src/scripts/router.js b/src/scripts/router.js index fc154b5..ecd6cbd 100644 --- a/src/scripts/router.js +++ b/src/scripts/router.js @@ -1,4 +1,4 @@ -import { route, router, getCurrentRoute } from '@riotjs/route'; +import { router, getCurrentRoute } from '@riotjs/route'; function baseUrl() { return getCurrentRoute().replace(/#!(.*)/, ''); @@ -11,4 +11,7 @@ export default { taglist(image) { router.push(`${baseUrl()}#!/taglist/${image}`); }, + getTagListImage() { + return getCurrentRoute().replace(/^.*(#!)?\/?taglist\//, ''); + } }; diff --git a/src/scripts/utils.js b/src/scripts/utils.js index 8623dfa..6290a2d 100644 --- a/src/scripts/utils.js +++ b/src/scripts/utils.js @@ -1,5 +1,4 @@ - -registryUI.bytesToSize = function (bytes) { +export function bytesToSize(bytes) { const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes == undefined || isNaN(bytes)) { return '?'; @@ -8,13 +7,26 @@ registryUI.bytesToSize = function (bytes) { } const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return Math.ceil(bytes / Math.pow(1024, i)) + ' ' + sizes[i]; -}; +} -registryUI.dateFormat = function(date) { +export function dateFormat(date) { if (date === undefined) { return ''; } - const labels = ['a second', 'seconds', 'a minute', 'minutes', 'an hour', 'hours', 'a day', 'days', 'a month', 'months', 'a year', 'years']; + const labels = [ + 'a second', + 'seconds', + 'a minute', + 'minutes', + 'an hour', + 'hours', + 'a day', + 'days', + 'a month', + 'months', + 'a year', + 'years', + ]; const maxSeconds = [1, 60, 3600, 86400, 2592000, 31104000, Infinity]; const diff = (new Date() - date) / 1000; for (var i = 0; i < maxSeconds.length - 1; i++) { @@ -24,10 +36,9 @@ registryUI.dateFormat = function(date) { return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1]; } } -}; +} - -registryUI.getHistoryIcon = function(attribute) { +export function getHistoryIcon(attribute) { switch (attribute) { case 'architecture': return 'memory'; @@ -63,29 +74,39 @@ registryUI.getHistoryIcon = function(attribute) { case 'ExposedPorts': return 'router'; default: - '' + ''; } } -registryUI.getPage = function(elts, page, limit) { - if (!limit) { limit = 100; } - if (!elts) { return []; } +export function getPage(elts, page, limit) { + if (!limit) { + limit = 100; + } + if (!elts) { + return []; + } return elts.slice((page - 1) * limit, limit * page); } -registryUI.getNumPages = function(elts, limit) { - if (!limit) { limit = 100; } - if (!elts) { return 0; } +export function getNumPages(elts, limit) { + if (!limit) { + limit = 100; + } + if (!elts) { + return 0; + } return Math.trunc(elts.length / limit) + 1; } -registryUI.getPageLabels = function(page, nPages) { +export function getPageLabels(page, nPages) { var pageLabels = []; var maxItems = 10; - if (nPages === 1) { return pageLabels; } + if (nPages === 1) { + return pageLabels; + } if (page !== 1 && nPages >= maxItems) { - pageLabels.push({'icon': 'first_page', page: 1}); - pageLabels.push({'icon': 'chevron_left', page: page - 1}); + pageLabels.push({ 'icon': 'first_page', page: 1 }); + pageLabels.push({ 'icon': 'chevron_left', page: page - 1 }); } var start = Math.round(Math.max(1, Math.min(page - maxItems / 2, nPages - maxItems + 1))); for (var i = start; i < Math.min(nPages + 1, start + maxItems); i++) { @@ -93,35 +114,39 @@ registryUI.getPageLabels = function(page, nPages) { page: i, current: i === page, 'space-left': page === 1 && nPages > maxItems, - 'space-right': page === nPages && nPages > maxItems + 'space-right': page === nPages && nPages > maxItems, }); } if (page !== nPages && nPages >= maxItems) { - pageLabels.push({'icon': 'chevron_right', page: page + 1}); - pageLabels.push({'icon': 'last_page', page: nPages}); + pageLabels.push({ 'icon': 'chevron_right', page: page + 1 }); + pageLabels.push({ 'icon': 'last_page', page: nPages }); } return pageLabels; } -registryUI.updateQueryString = function(qs) { +export function updateQueryString(qs) { var search = ''; for (var key in qs) { if (qs[key] !== undefined) { - search += (search.length > 0 ? '&' : '?') +key + '=' + qs[key]; + search += (search.length > 0 ? '&' : '?') + key + '=' + qs[key]; } } history.pushState(null, '', search + window.location.hash); } -registryUI.stripHttps = function (url) { +export function stripHttps(url) { if (!url) { return ''; } return url.replace(/^https?:\/\//, ''); -}; +} -registryUI.eventTransfer = function(from, to) { - from.on('*', function(event, param) { +export function eventTransfer(from, to) { + from.on('*', function (event, param) { to.trigger(event, param); - }) -} \ No newline at end of file + }); +} + +export function isDigit(char) { + return char >= '0' && char <= '9'; +};