mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-05-03 10:29:53 +03:00
feat: allow sorting of tags by creation date and size (#125)
closes #125
This commit is contained in:
parent
3bfe107e3c
commit
e990c39a18
5 changed files with 107 additions and 43 deletions
|
@ -16,7 +16,7 @@
|
||||||
-->
|
-->
|
||||||
<copy-to-clipboard>
|
<copy-to-clipboard>
|
||||||
<div class="copy-to-clipboard">
|
<div class="copy-to-clipboard">
|
||||||
<input style="display: none; width: 1px; height: 1px;" value="{ state.dockerCmd }">
|
<input style="display: none; width: 1px; height: 1px;" value="{ getDockerCmd(props) }">
|
||||||
<material-button waves-center="true" rounded="true" waves-color="#ddd" onClick="{ copy }"
|
<material-button waves-center="true" rounded="true" waves-color="#ddd" onClick="{ copy }"
|
||||||
title="Copy pull command.">
|
title="Copy pull command.">
|
||||||
<i class="material-icons">content_copy</i>
|
<i class="material-icons">content_copy</i>
|
||||||
|
@ -27,33 +27,40 @@
|
||||||
ERROR_CAN_NOT_READ_CONTENT_DIGEST
|
ERROR_CAN_NOT_READ_CONTENT_DIGEST
|
||||||
} from '../../scripts/utils';
|
} from '../../scripts/utils';
|
||||||
export default {
|
export default {
|
||||||
onBeforeMount(props, state) {
|
onMounted(props, state) {
|
||||||
|
this.load(props, state);
|
||||||
|
},
|
||||||
|
onUpdated(props, state) {
|
||||||
|
this.load(props, state);
|
||||||
|
},
|
||||||
|
getDockerCmd(props) {
|
||||||
if (props.target === 'tag') {
|
if (props.target === 'tag') {
|
||||||
state.dockerCmd = `docker pull ${props.pullUrl}/${props.image.name}:${props.image.tag}`;
|
return `docker pull ${props.pullUrl}/${props.image.name}:${props.image.tag}`;
|
||||||
|
} else {
|
||||||
|
return `docker pull ${props.pullUrl}/${props.image.name}@${props.image.digest}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMounted(props, state) {
|
load(props, state) {
|
||||||
if (props.target !== 'tag') {
|
if (props.target !== 'tag' && !props.image.digest) {
|
||||||
props.image.one('content-digest', (digest) => {
|
props.image.one('content-digest', (digest) => {
|
||||||
this.update({
|
this.update()
|
||||||
dockerCmd: `docker pull ${props.pullUrl}/${props.image.name}@${digest}`
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
props.image.trigger('get-content-digest');
|
props.image.trigger('get-content-digest');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
copy() {
|
copy() {
|
||||||
if (!this.state.dockerCmd) {
|
const copyText = this.$('input');
|
||||||
|
if (!copyText.value) {
|
||||||
this.props.onNotify(ERROR_CAN_NOT_READ_CONTENT_DIGEST);
|
this.props.onNotify(ERROR_CAN_NOT_READ_CONTENT_DIGEST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const copyText = this.$('input');
|
|
||||||
copyText.style.display = 'block';
|
copyText.style.display = 'block';
|
||||||
copyText.select();
|
copyText.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
copyText.style.display = 'none';
|
copyText.style.display = 'none';
|
||||||
|
|
||||||
this.props.onNotify('`' + this.state.dockerCmd + '` has been copied to clipboard.')
|
this.props.onNotify('`' + copyText.value + '` has been copied to clipboard.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,11 +15,20 @@ Copyright (C) 2016-2021 Jones Magloire @Joxit
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<image-content-digest>
|
<image-content-digest>
|
||||||
<div title="{ state.title }">{ state.displayId }</div>
|
<div title="{ getTitle(props.image, state.chars) }">{ getDigest(props.image, state.chars) }</div>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
onMounted(props) {
|
onMounted(props, state) {
|
||||||
this.chars = -1;
|
this.load(props, state);
|
||||||
|
},
|
||||||
|
onUpdated(props, state) {
|
||||||
|
this.load(props, state);
|
||||||
|
},
|
||||||
|
load(props, state) {
|
||||||
|
if (props.image.digest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.chars = -1;
|
||||||
props.image.one('content-digest', (digest) => {
|
props.image.one('content-digest', (digest) => {
|
||||||
this.digest = digest;
|
this.digest = digest;
|
||||||
props.image.on('content-digest-chars', this.onResize);
|
props.image.on('content-digest-chars', this.onResize);
|
||||||
|
@ -28,22 +37,23 @@ Copyright (C) 2016-2021 Jones Magloire @Joxit
|
||||||
props.image.trigger('get-content-digest');
|
props.image.trigger('get-content-digest');
|
||||||
},
|
},
|
||||||
onResize(chars) {
|
onResize(chars) {
|
||||||
if (chars === this.chars) {
|
if (chars !== this.state.chars) {
|
||||||
return;
|
this.update({
|
||||||
}
|
chars
|
||||||
let displayId = this.digest;
|
});
|
||||||
let title = '';
|
}
|
||||||
this.chars = chars;
|
},
|
||||||
if (chars >= 70) {
|
getTitle(image, chars) {
|
||||||
displayId = this.digest;
|
return chars >= 70 ? '' : (image.digest || '');
|
||||||
} else if (chars <= 0) {
|
},
|
||||||
displayId = '';
|
getDigest(image, chars) {
|
||||||
title = this.digest;
|
if (chars >= 70) {
|
||||||
} else {
|
return image.digest || '';
|
||||||
displayId = this.digest.slice(0, chars) + '...';
|
} else if (chars <= 0) {
|
||||||
title = this.digest;
|
return '';
|
||||||
}
|
} else {
|
||||||
this.update({title, displayId});
|
return image.digest && image.digest.slice(0, chars) + '...';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,17 +15,14 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<image-date>
|
<image-date>
|
||||||
<div title="Creation date { state.localDate }">{ dateFormat(state.date) } ago</div>
|
<div title="Creation date { getLocalDate(props.image) }">{ getDate(props.image) } ago</div>
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
dateFormat,
|
dateFormat,
|
||||||
} from '../../scripts/utils';
|
} from '../../scripts/utils';
|
||||||
export default {
|
export default {
|
||||||
state: {
|
|
||||||
localDate: 'unknown'
|
|
||||||
},
|
|
||||||
onMounted(props) {
|
onMounted(props) {
|
||||||
props.image.on('creation-date', (date) => {
|
props.image.one('creation-date', (date) => {
|
||||||
this.update({
|
this.update({
|
||||||
date: date,
|
date: date,
|
||||||
localDate: date.toLocaleString()
|
localDate: date.toLocaleString()
|
||||||
|
@ -33,7 +30,12 @@
|
||||||
});
|
});
|
||||||
props.image.trigger('get-date');
|
props.image.trigger('get-date');
|
||||||
},
|
},
|
||||||
dateFormat
|
getDate(image) {
|
||||||
|
return dateFormat(image.creationDate)
|
||||||
|
},
|
||||||
|
getLocalDate(image) {
|
||||||
|
return (image.creationDate && image.creationDate.toLocaleString()) || 'unknown'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</image-date>
|
</image-date>
|
|
@ -15,21 +15,33 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<image-size>
|
<image-size>
|
||||||
<div title="Compressed size of your image.">{ bytesToSize(state.size) }</div>
|
<div title="Compressed size of your image.">{ getImageSize(props.image) }</div>
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
bytesToSize,
|
bytesToSize,
|
||||||
} from '../../scripts/utils';
|
} from '../../scripts/utils';
|
||||||
export default {
|
export default {
|
||||||
onMounted(props) {
|
onMounted(props, state) {
|
||||||
|
this.load(props, state);
|
||||||
|
},
|
||||||
|
onUpdated(props, state) {
|
||||||
|
this.load(props, state);
|
||||||
|
},
|
||||||
|
load(props, state) {
|
||||||
|
if (typeof props.image.size === 'number') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
props.image.on('size', (size) => {
|
props.image.on('size', (size) => {
|
||||||
this.update({
|
this.update({
|
||||||
size
|
size
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
props.image.trigger('get-size');
|
props.image.trigger('get-size');
|
||||||
|
|
||||||
},
|
},
|
||||||
bytesToSize
|
getImageSize(image) {
|
||||||
|
return bytesToSize(image.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</image-size>
|
</image-size>
|
|
@ -19,13 +19,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
<table style="border: none;">
|
<table style="border: none;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="creation-date">Creation date</th>
|
<th
|
||||||
<th class="image-size">Size</th>
|
class="creation-date { (state.desc && state.orderType === 'date') ? 'material-card-th-sorted-descending' : 'material-card-th-sorted-ascending' }"
|
||||||
|
onclick="{() => onPageReorder('date') }">
|
||||||
|
Creation date
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="image-size { (state.desc && state.orderType === 'size') ? 'material-card-th-sorted-descending' : 'material-card-th-sorted-ascending' }"
|
||||||
|
onclick="{() => onPageReorder('size') }">
|
||||||
|
Size
|
||||||
|
</th>
|
||||||
<th id="image-content-digest-header" if="{ props.showContentDigest }">Content Digest</th>
|
<th id="image-content-digest-header" if="{ props.showContentDigest }">Content Digest</th>
|
||||||
|
|
||||||
<th id="image-tag-header"
|
<th id="image-tag-header"
|
||||||
class="{ props.asc ? 'material-card-th-sorted-ascending' : 'material-card-th-sorted-descending' }"
|
class="{ props.asc ? 'material-card-th-sorted-ascending' : 'material-card-th-sorted-descending' }"
|
||||||
onclick="{() => props.onReverseOrder() }">Tag
|
onclick="{ onReverseOrder }">Tag
|
||||||
</th>
|
</th>
|
||||||
<th class="show-tag-history">History</th>
|
<th class="show-tag-history">History</th>
|
||||||
<th class="remove-tag { state.toDelete.size > 0 ? 'delete' : '' }" if="{ props.isImageRemoveActivated }">
|
<th class="remove-tag { state.toDelete.size > 0 ? 'delete' : '' }" if="{ props.isImageRemoveActivated }">
|
||||||
|
@ -138,7 +146,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
toDelete: this.state.toDelete
|
toDelete: this.state.toDelete
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getPage,
|
onReverseOrder() {
|
||||||
|
this.state.orderType = null;
|
||||||
|
this.state.desc = false;
|
||||||
|
this.props.onReverseOrder();
|
||||||
|
},
|
||||||
|
onPageReorder(type) {
|
||||||
|
this.update({
|
||||||
|
orderType: type,
|
||||||
|
desc: (this.state.orderType && this.state.orderType !== type) || !this.state.desc
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getPage(tags, page) {
|
||||||
|
const sortedTags = getPage(tags, page);
|
||||||
|
if (this.state.orderType === 'date') {
|
||||||
|
sortedTags.sort((e1, e2) =>
|
||||||
|
!this.state.desc ?
|
||||||
|
e2.creationDate.getTime() - e1.creationDate.getTime() :
|
||||||
|
e1.creationDate.getTime() - e2.creationDate.getTime());
|
||||||
|
} else if (this.state.orderType === 'size') {
|
||||||
|
sortedTags.sort((e1, e2) =>
|
||||||
|
!this.state.desc ?
|
||||||
|
e2.size - e1.size :
|
||||||
|
e1.size - e2.size);
|
||||||
|
}
|
||||||
|
return sortedTags;
|
||||||
|
},
|
||||||
matchSearch
|
matchSearch
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue