feat(taglist): improve visibility of multi-architecture images (#271)

closes #271
This commit is contained in:
Joxit 2023-06-03 15:33:25 +02:00
parent affb0572c9
commit 4091baa341
No known key found for this signature in database
GPG Key ID: F526592B8E012263
4 changed files with 88 additions and 6 deletions

View File

@ -0,0 +1,54 @@
<architectures>
<div class="architecture" each="{ architecture in state.architectures }">{ architecture }</div>
<script>
import { platformToString } from '../../scripts/docker-image';
export default {
onMounted(props, state) {
this.onLoad(props, state);
},
onUpdated(props, state) {
if (props.image !== state.image) {
this.onLoad(props, state);
}
},
onLoad(props, state) {
if (props.image.manifests) {
return this.onList(props.image.manifests);
} else if (props.image.blobs) {
return this.onBlobs(props.image.blobs);
}
props.image.on('blobs', (blobs) => this.onBlobs(blobs, props, state));
props.image.on('list', (list) => this.onList(list, props, state));
},
onBlobs(blobs, props, state) {
const architectures = [platformToString(blobs)];
if (!props.image.manifests) {
this.update({ architectures, image: props.image });
}
},
onList(list, props, state) {
const architectures = list.map(({ platform }) => platformToString(platform));
this.update({ architectures, image: props.image });
},
};
</script>
<style>
:host architectures {
display: flex;
flex-direction: column;
}
architectures .architecture {
background-color: var(--hover-background);
padding: 2px 4px;
border-radius: 12px;
margin: 4px 0;
text-align: center;
}
architectures .architecture:first-child {
margin-top: 0;
}
architectures .architecture:last-child {
margin-bottom: 0;
}
</style>
</architectures>

View File

@ -49,6 +49,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
Tag
</th>
<th class="architectures">Arch</th>
<th class="show-tag-history">History</th>
<th
class="remove-tag { state.toDelete.size > 0 && !state.singleDeleteAction ? 'delete' : '' }"
@ -103,6 +104,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-notify="{ props.onNotify }"
></copy-to-clipboard>
</td>
<td class="architectures">
<architectures image="{ image }"></architectures>
</td>
<td class="show-tag-history">
<tag-history-button image="{ image }"></tag-history-button>
</td>
@ -130,6 +134,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import CopyToClipboard from './copy-to-clipboard.riot';
import TagHistoryButton from './tag-history-button.riot';
import RemoveImage from './remove-image.riot';
import Architectures from './architectures.riot';
import { matchSearch } from '../search-bar.riot';
import ConfirmDeleteImage from '../dialogs/confirm-delete-image.riot';
const ACTION_CHECK_TO_DELETE = 'CHECK';
@ -146,6 +151,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
RemoveImage,
TagHistoryButton,
ConfirmDeleteImage,
Architectures,
},
onBeforeMount(props) {
this.state = {
@ -277,4 +283,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
};
export { ACTION_CHECK_TO_DELETE, ACTION_UNCHECK_TO_DELETE, ACTION_DELETE_IMAGE };
</script>
<style>
tag-table table th.architectures {
text-align: center;
}
</style>
</tag-table>

View File

@ -34,6 +34,13 @@ export const filterWrongManifests = (response) => {
);
};
export const platformToString = (platform) => {
if (!platform || !platform.architecture) {
return 'unknown';
}
return platform.architecture + (platform.variant ? platform.variant : '');
};
export class DockerImage {
constructor(name, tag, { list, registryUrl, onNotify, onAuthentication, useControlCacheHeader }) {
this.name = name;
@ -90,7 +97,9 @@ export class DockerImage {
if (this.status === 200 || this.status === 202) {
const response = JSON.parse(this.responseText);
if (supportListManifest(response) && self.opts.list) {
self.trigger('list', filterWrongManifests(response));
const manifests = filterWrongManifests(response);
self.trigger('list', manifests);
self.manifests = manifests;
const manifest = response.manifests[0];
const image = new DockerImage(self.name, manifest.digest, { ...self.opts, list: false });
eventTransfer(image, self);

View File

@ -1,4 +1,4 @@
import { supportListManifest, filterWrongManifests } from '../src/scripts/docker-image.js';
import { supportListManifest, filterWrongManifests, platformToString } from '../src/scripts/docker-image.js';
import { dockerManifestList } from './fixtures/docker-manifest-list.js';
import { ociImageIndexLayer } from './fixtures/oci-image-index-layer.js';
import { ociImageIndexManifest } from './fixtures/oci-image-index-manifest.js';
@ -36,10 +36,18 @@ describe('docker-image', () => {
);
});
it('should return all manifests for `application/vnd.oci.image.index.v1+json`', () => {
assert.equal(
filterWrongManifests(ociImageIndexManifest['application/vnd.oci.image.index.v1+json']).length,
2
);
assert.equal(filterWrongManifests(ociImageIndexManifest['application/vnd.oci.image.index.v1+json']).length, 2);
});
});
describe('platformToString', () => {
it('should return unknown when the platform is not found', () => {
assert.equal(platformToString(), 'unknown');
assert.equal(platformToString({}), 'unknown');
});
it('should format the platform', () => {
assert.equal(platformToString({ os: 'linux', architecture: 'amd64' }), 'amd64');
assert.equal(platformToString({ os: 'linux', architecture: 'arm', variant: 'v7' }), 'armv7');
assert.equal(platformToString({ architecture: 'arm', variant: 'v7' }), 'armv7');
});
});
});