docker-registry-ui/src/components/tag-history/tag-history.riot

292 lines
8.5 KiB
Plaintext

<!--
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<tag-history>
<material-card>
<div class="material-card-title-action">
<material-button
text-color="var(--neutral-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
rounded="true"
href="{ toTaglist() }"
icon
>
<i class="material-icons">arrow_back</i>
</material-button>
<h2>History of { props.image }:{ props.tag } <i class="material-icons">history</i></h2>
<material-button
text-color="var(--accent-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
rounded="true"
outlined
onClick="{ showDockerfile }"
>
Dockerfile
</material-button>
</div>
</material-card>
<div if="{ !state.loadend }" class="spinner-wrapper">
<material-spinner></material-spinner>
</div>
<dockerfile
opened="{ state.showDockerfile }"
on-close="{ onDockerfileClose }"
elements="{ state.elements }"
></dockerfile>
<material-tabs
if="{ state.archs && state.loadend }"
color="var(--background)"
text-color="var(--primary-text)"
text-selected-color="var(--accent-text)"
line-color="var(--background)"
line-selected-color="var(--accent-text)"
useLine="{ true }"
tabs="{ state.archs }"
onTabChanged="{ onTabChanged }"
></material-tabs>
<material-card each="{ element in state.elements }">
<tag-history-element
each="{ entry in element }"
if="{ entry.value && entry.value.length > 0}"
entry="{ entry }"
></tag-history-element>
</material-card>
<script>
import { DockerImage } from '../../scripts/docker-image';
import { bytesToSize } from '../../scripts/utils';
import Dockerfile from '../dialogs/dockerfile.riot';
import router from '../../scripts/router';
import TagHistoryElement from './tag-history-element.riot';
export default {
components: {
TagHistoryElement,
Dockerfile,
},
onBeforeMount(props, state) {
state.elements = [];
state.image = new DockerImage(props.image, props.tag, {
list: true,
registryUrl: props.registryUrl,
onNotify: props.onNotify,
onAuthentication: props.onAuthentication,
useControlCacheHeader: props.useControlCacheHeader,
});
state.image.fillInfo();
},
onMounted(props, state) {
state.image.on('blobs', this.processBlobs);
state.image.on('list', this.multiArchList);
},
onTabChanged(arch, idx) {
const state = this.state;
const { registryUrl, onNotify, useControlCacheHeader } = this.props;
state.elements = [];
state.image.variants[idx] =
state.image.variants[idx] ||
new DockerImage(this.props.image, arch.digest, {
list: false,
registryUrl,
onNotify,
useControlCacheHeader,
});
if (state.image.variants[idx].blobs) {
return this.processBlobs(state.image.variants[idx].blobs);
}
state.image.variants[idx].fillInfo();
state.image.variants[idx].on('blobs', this.processBlobs);
},
processBlobs(blobs) {
const state = this.state;
const { historyCustomLabels } = this.props;
function exec(elt) {
const guiElements = [];
for (var attribute in elt) {
if (elt.hasOwnProperty(attribute) && attribute != 'empty_layer') {
const value = elt[attribute];
const guiElement = modifySpecificAttributeTypes(attribute, value);
guiElements.push(guiElement);
}
}
return guiElements.sort(eltSort);
}
const elements = new Array(blobs.history.length + 1);
elements[0] = exec(getConfig(blobs, { historyCustomLabels }));
blobs.history.forEach(function (elt, i) {
elements[blobs.history.length - i] = exec(elt);
});
this.update({
elements,
loadend: true,
});
},
multiArchList(manifests) {
manifests = manifests.manifests || manifests;
const archs = manifests.map(function (manifest) {
return {
title:
manifest.platform.os +
'/' +
manifest.platform.architecture +
(manifest.platform.variant ? manifest.platform.variant : ''),
digest: manifest.digest,
};
});
this.update({
archs,
});
},
toTaglist() {
return router.taglist(this.props.image);
},
showDockerfile() {
console.log(this);
this.update({ showDockerfile: true });
},
onDockerfileClose() {
this.update({ showDockerfile: false });
},
};
const eltIdx = function (e) {
switch (e) {
case 'created':
return 1;
case 'created_by':
return 2;
case 'size':
return 3;
case 'os':
return 4;
case 'architecture':
return 5;
case 'id':
return 6;
case 'linux':
return 7;
case 'docker_version':
return 8;
default:
return 10;
}
};
const eltSort = function (e1, e2) {
return eltIdx(e1.key) - eltIdx(e2.key);
};
const parseCreatedBy = (value) => {
if (value.startsWith('COPY')) {
return {
value: 'COPY',
content: value.replace(/^COPY /, ''),
};
}
let cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+) (.*)/);
return {
value: (cmd && cmd[1]) || 'RUN',
content: (cmd && cmd[2]) || value.replace(/^\/bin\/sh *-c *(#\(nop\))?/, ''),
};
};
const modifySpecificAttributeTypes = function (key, value) {
switch (key) {
case 'created':
return { key, value: new Date(value).toLocaleString() };
case 'created_by':
const cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+) (.*)/);
return {
key,
...parseCreatedBy(value),
};
case 'size':
return { key, value: bytesToSize(value) };
case 'Entrypoint':
case 'Cmd':
return { key, value: (value || []).join(' ') };
case 'Labels':
return {
key,
value: Object.keys(value || {}).map(function (elt) {
return value[elt] ? elt + '=' + value[elt] : '';
}),
};
case 'Volumes':
case 'ExposedPorts':
return { key, value: Object.keys(value) };
}
return { key, value: value || '' };
};
const getConfig = function (blobs, { historyCustomLabels }) {
const res = [
'architecture',
'User',
'created',
'docker_version',
'os',
'Cmd',
'Entrypoint',
'Env',
'Labels',
'User',
'Volumes',
'WorkingDir',
'author',
'id',
'ExposedPorts',
].reduce(function (acc, e) {
const value = blobs[e] || blobs.config[e];
if (value && e === 'architecture' && blobs.variant) {
acc[e] = value + blobs.variant;
} else if (value) {
acc[e] = value;
}
return acc;
}, {});
if (!res.author && res.Labels && res.Labels.maintainer) {
res.author = blobs.config.Labels.maintainer;
delete res.Labels.maintainer;
}
if (res.Labels) {
historyCustomLabels
.filter((label) => res.Labels[label])
.forEach((label) => {
res[`custom-label-${label}`] = res.Labels[label];
delete res.Labels[label];
});
}
return res;
};
</script>
<style>
h2 {
flex-grow: 1;
display: flex;
flex-direction: row;
align-items: center;
}
</style>
</tag-history>