feat: expose some custom labels

This commit is contained in:
Joxit 2022-03-23 09:18:20 +01:00
parent 19e72e4a5f
commit ba6d817b41
No known key found for this signature in database
GPG key ID: F526592B8E012263
7 changed files with 182 additions and 129 deletions

View file

@ -100,6 +100,7 @@ Some env options are available for use this interface for **only one server**.
- `DEFAULT_REGISTRIES`: List of comma separated registry URLs (e.g `http://registry.example.com,http://registry:5000`), available only when `SINGLE_REGISTRY=false`. (default: ` `).
- `READ_ONLY_REGISTRIES`: Desactivate dialog for remove and add new registries, available only when `SINGLE_REGISTRY=false`. (default: `false`).
- `SHOW_CATALOG_NB_TAGS`: Show number of tags per images on catalog page. This will produce + nb images requests, not recommended on large registries. (default: `false`).
- `HISTORY_CUSTOM_LABELS`: Expose custom labels in history page, custom labels will be processed like maintainer label.
There are some examples with [docker-compose](https://docs.docker.com/compose/) and docker-registry-ui as proxy [here](https://github.com/Joxit/docker-registry-ui/tree/main/examples/ui-as-proxy/) or docker-registry-ui as standalone [here](https://github.com/Joxit/docker-registry-ui/tree/main/examples/ui-as-standalone/).

View file

@ -9,6 +9,7 @@ sed -i "s~\${SHOW_CONTENT_DIGEST}~${SHOW_CONTENT_DIGEST}~" index.html
sed -i "s~\${DEFAULT_REGISTRIES}~${DEFAULT_REGISTRIES}~" index.html
sed -i "s~\${READ_ONLY_REGISTRIES}~${READ_ONLY_REGISTRIES}~" index.html
sed -i "s~\${SHOW_CATALOG_NB_TAGS}~${SHOW_CATALOG_NB_TAGS}~" index.html
sed -i "s~\${HISTORY_CUSTOM_LABELS}~${HISTORY_CUSTOM_LABELS}~" index.html
if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
sed -i "s/\${DELETE_IMAGES}/false/" index.html
@ -64,4 +65,4 @@ if [ "$(whoami)" != "root" ]; then
sed -i "s,/var/run/nginx.pid,/tmp/nginx.pid," /etc/nginx/nginx.conf
fi
sed -i "s,listen 80;,listen $NGINX_LISTEN_PORT;," /etc/nginx/conf.d/default.conf
sed -i "s,listen 80;,listen $NGINX_LISTEN_PORT;," /etc/nginx/conf.d/default.conf

View file

@ -42,7 +42,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tag-history registry-url="{ state.registryUrl }" registry-name="{ state.name }" pull-url="{ state.pullUrl }"
image="{ router.getTagHistoryImage() }" tag="{ router.getTagHistoryTag() }"
is-image-remove-activated="{ truthy(props.isImageRemoveActivated) }" on-notify="{ notifySnackbar }"
on-authentication="{ onAuthentication }"></tag-history>
on-authentication="{ onAuthentication }" history-custom-labels="{ stringToArray(props.historyCustomLabels) }"></tag-history>
</route>
</router>
<registry-authentication realm="{ state.realm }" scope="{ state.scope }" service="{ state.service }"
@ -80,7 +80,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
stripHttps,
getRegistryServers,
setRegistryServers,
truthy
truthy,
stringToArray
} from '../scripts/utils';
import router from '../scripts/router';
@ -175,7 +176,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
baseRoute: '([^#]*?)/(\\?[^#]*?)?(#!)?(/?)',
router,
version,
truthy
truthy,
stringToArray
}
</script>
</docker-registry-ui>

View file

@ -15,17 +15,16 @@ 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-element class="{ state.key }">
<div class="headline"><i class="material-icons">{ state.icon }</i>
<div class="headline">
<i class="material-icons">{ state.icon }</i>
<p>{ state.name }</p>
</div>
<div class="content">
<div class="value" if="{ state.value }"> { state.value }</div>
<div class="values value" each="{ value in state.values }" if="{ state.values }"> { value }</div>
<div class="value" if="{ state.value }">{ state.value }</div>
<div class="values value" each="{ value in state.values }" if="{ state.values }">{ value }</div>
</div>
<script>
import {
getHistoryIcon
} from '../../scripts/utils';
import { getHistoryIcon } from '../../scripts/utils';
export default {
onBeforeStart(props, state) {
state.key = props.entry.key;
@ -48,14 +47,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return name;
} else if (name === 'os') {
return 'OS';
} else if (name.startsWith('custom-label-')) {
name = name.replace('custom-label-', '');
}
return name.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace('_', ' ')
return name
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/[_-]/g, ' ')
.split(' ')
.map(word => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
.join(' ');
}
}
},
};
</script>
<style>
:host.Labels .value,
@ -70,7 +72,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:host.docker_version .headline .material-icons {
background-size: 24px auto;
background-image: url("images/docker-logo.svg");
background-image: url('images/docker-logo.svg');
background-repeat: no-repeat;
}
@ -103,4 +105,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
font-size: 12px;
}
</style>
</tag-history-element>
</tag-history-element>

View file

@ -20,33 +20,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<material-button waves-center="true" rounded="true" waves-color="#ddd" onClick="{ toTaglist }">
<i class="material-icons">arrow_back</i>
</material-button>
<h2>
History of { props.image }:{ props.tag } <i class="material-icons">history</i>
</h2>
<h2>History of { props.image }:{ props.tag } <i class="material-icons">history</i></h2>
</div>
</material-card>
<div if="{ !state.loadend }" class="spinner-wrapper">
<material-spinner />
<material-spinner></material-spinner>
</div>
<material-tabs if="{ state.archs && state.loadend }" useLine="{ true }" tabs="{ state.archs }"
onTabChanged="{ onTabChanged }" />
<material-tabs
if="{ state.archs && state.loadend }"
useLine="{ true }"
tabs="{ state.archs }"
onTabChanged="{ onTabChanged }"
></material-tabs>
<material-card each="{ element in state.elements }" class="tag-history-element">
<tag-history-element each="{ entry in element }" if="{ entry.value && entry.value.length > 0}" entry="{ entry }" />
<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 { DockerImage } from '../../scripts/docker-image';
import { bytesToSize } from '../../scripts/utils';
import router from '../../scripts/router';
import TagHistoryElement from './tag-history-element.riot'
import TagHistoryElement from './tag-history-element.riot';
export default {
components: {
TagHistoryElement
TagHistoryElement,
},
onBeforeMount(props, state) {
state.elements = [];
@ -54,9 +56,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
list: true,
registryUrl: props.registryUrl,
onNotify: props.onNotify,
onAuthentication: props.onAuthentication
onAuthentication: props.onAuthentication,
});
state.image.fillInfo()
state.image.fillInfo();
},
onMounted(props, state) {
state.image.on('blobs', this.processBlobs);
@ -64,16 +66,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
},
onTabChanged(arch, idx) {
const state = this.state;
const {
registryUrl,
onNotify
} = this.props;
state.elements = []
state.image.variants[idx] = state.image.variants[idx] ||
const { registryUrl, onNotify } = this.props;
state.elements = [];
state.image.variants[idx] =
state.image.variants[idx] ||
new DockerImage(this.props.image, arch.digest, {
list: false,
registryUrl,
onNotify
onNotify,
});
if (state.image.variants[idx].blobs) {
return this.processBlobs(state.image.variants[idx].blobs);
@ -83,6 +83,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
},
processBlobs(blobs) {
const state = this.state;
const { historyCustomLabels } = this.props;
function exec(elt) {
const guiElements = [];
@ -90,8 +91,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
if (elt.hasOwnProperty(attribute) && attribute != 'empty_layer') {
const value = elt[attribute];
const guiElement = {
"key": attribute,
"value": modifySpecificAttributeTypes(attribute, value)
'key': attribute,
'value': modifySpecificAttributeTypes(attribute, value),
};
guiElements.push(guiElement);
}
@ -99,32 +100,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return guiElements.sort(eltSort);
}
const elements = new Array(blobs.history.length + 1);
elements[0] = exec(getConfig(blobs));
elements[0] = exec(getConfig(blobs, { historyCustomLabels }));
blobs.history.forEach(function (elt, i) {
elements[blobs.history.length - i] = exec(elt)
elements[blobs.history.length - i] = exec(elt);
});
this.update({
elements,
loadend: true
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
}
title:
manifest.platform.os +
'/' +
manifest.platform.architecture +
(manifest.platform.variant ? manifest.platform.variant : ''),
digest: manifest.digest,
};
});
this.update({
archs
archs,
});
},
toTaglist() {
router.taglist(this.props.image);
}
}
},
};
const eltIdx = function (e) {
switch (e) {
case 'created':
@ -158,7 +162,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return new Date(value).toLocaleString();
case 'created_by':
const cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);
return (cmd && cmd[1]) || 'RUN'
return (cmd && cmd[1]) || 'RUN';
case 'size':
return bytesToSize(value);
case 'Entrypoint':
@ -175,26 +179,47 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return value || '';
};
const getConfig = function (blobs) {
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;
}, {});
const getConfig = function (blobs, { historyCustomLabels }) {
console.log(this);
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)) {
if (!res.author && res.Labels && res.Labels.maintainer) {
res.author = blobs.config.Labels.maintainer;
delete res.Labels.maintainer;
}
historyCustomLabels
.filter((label) => res.Labels[label])
.forEach((label) => {
res[`custom-label-${label}`] = res.Labels[label];
delete res.Labels[label];
});
return res;
};
</script>
</tag-history>
</tag-history>

View file

@ -16,63 +16,78 @@
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- build:css docker-registry-ui.css -->
<link href="../node_modules/riot-mui/build/styles/riot-mui.min.css" rel="stylesheet" type="text/css" />
<link href="style.css" rel="stylesheet" type="text/css" />
<link href="material-icons.css" rel="stylesheet" type="text/css" />
<link href="roboto.css" rel="stylesheet" type="text/css" />
<!-- endbuild -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:site_name" content="Docker Registry UI" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@Joxit" />
<meta name="twitter:creator" content="@Jones Magloire" />
<title>Docker Registry UI</title>
</head>
<head>
<meta charset="UTF-8">
<!-- build:css docker-registry-ui.css -->
<link href="../node_modules/riot-mui/build/styles/riot-mui.min.css" rel="stylesheet" type="text/css">
<link href="style.css" rel="stylesheet" type="text/css">
<link href="material-icons.css" rel="stylesheet" type="text/css">
<link href="roboto.css" rel="stylesheet" type="text/css">
<!-- endbuild -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:site_name" content="Docker Registry UI" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@Joxit" />
<meta name="twitter:creator" content="@Jones Magloire" />
<title>Docker Registry UI</title>
</head>
<body>
<!-- build:keep production -->
<docker-registry-ui registry-url="${REGISTRY_URL}" name="${REGISTRY_TITLE}" pull-url="${PULL_URL}"
show-content-digest="${SHOW_CONTENT_DIGEST}" is-image-remove-activated="${DELETE_IMAGES}"
catalog-elements-limit="${CATALOG_ELEMENTS_LIMIT}" single-registry="${SINGLE_REGISTRY}"
default-registries="${DEFAULT_REGISTRIES}" read-only-registries="${READ_ONLY_REGISTRIES}"
show-catalog-nb-tags="${SHOW_CATALOG_NB_TAGS}">
</docker-registry-ui>
<!-- endbuild -->
<!-- build:keep developement -->
<docker-registry-ui registry-url="" name="Developement Registry" pull-url="" show-content-digest="true"
is-image-remove-activated="true" catalog-elements-limit="1000" single-registry="false" show-catalog-nb-tags="true">
</docker-registry-ui>
<!-- endbuild -->
<!-- build:js docker-registry-ui.js -->
<script src="../node_modules/riot/riot+compiler.min.js"></script>
<script src="../node_modules/riot-route/dist/route.js"></script>
<script src="../node_modules/riot-mui/build/js/riot-mui.js"></script>
<script src="tags/catalog.riot" type="riot/tag"></script>
<script src="tags/catalog-element.riot" type="riot/tag"></script>
<script src="tags/tag-history-button.riot" type="riot/tag"></script>
<script src="tags/tag-history.riot" type="riot/tag"></script>
<script src="tags/tag-history-element.riot" type="riot/tag"></script>
<script src="tags/taglist.riot" type="riot/tag"></script>
<script src="tags/image-tag.riot" type="riot/tag"></script>
<script src="tags/remove-image.riot" type="riot/tag"></script>
<script src="tags/copy-to-clipboard.riot" type="riot/tag"></script>
<script src="tags/dialogs/add.riot" type="riot/tag"></script>
<script src="tags/dialogs/change.riot" type="riot/tag"></script>
<script src="tags/dialogs/remove.riot" type="riot/tag"></script>
<script src="tags/dialogs/menu.riot" type="riot/tag"></script>
<script src="tags/image-size.riot" type="riot/tag"></script>
<script src="tags/image-date.riot" type="riot/tag"></script>
<script src="tags/image-content-digest.riot" type="riot/tag"></script>
<script src="tags/pagination.riot" type="riot/tag"></script>
<script src="tags/app.riot" type="riot/tag"></script>
<script src="scripts/http.js"></script>
<script src="scripts/script.js"></script>
<script src="scripts/utils.js"></script>
<!-- endbuild -->
</body>
</html>
<body>
<!-- build:keep production -->
<docker-registry-ui
registry-url="${REGISTRY_URL}"
name="${REGISTRY_TITLE}"
pull-url="${PULL_URL}"
show-content-digest="${SHOW_CONTENT_DIGEST}"
is-image-remove-activated="${DELETE_IMAGES}"
catalog-elements-limit="${CATALOG_ELEMENTS_LIMIT}"
single-registry="${SINGLE_REGISTRY}"
default-registries="${DEFAULT_REGISTRIES}"
read-only-registries="${READ_ONLY_REGISTRIES}"
show-catalog-nb-tags="${SHOW_CATALOG_NB_TAGS}"
history-custom-labels="${HISTORY_CUSTOM_LABELS}"
>
</docker-registry-ui>
<!-- endbuild -->
<!-- build:keep developement -->
<docker-registry-ui
registry-url=""
name="Developement Registry"
pull-url=""
show-content-digest="true"
is-image-remove-activated="true"
catalog-elements-limit="1000"
single-registry="false"
show-catalog-nb-tags="true"
history-custom-labels="first_custom_labels,second_custom_labels"
>
</docker-registry-ui>
<!-- endbuild -->
<!-- build:js docker-registry-ui.js -->
<script src="../node_modules/riot/riot+compiler.min.js"></script>
<script src="../node_modules/riot-route/dist/route.js"></script>
<script src="../node_modules/riot-mui/build/js/riot-mui.js"></script>
<script src="tags/catalog.riot" type="riot/tag"></script>
<script src="tags/catalog-element.riot" type="riot/tag"></script>
<script src="tags/tag-history-button.riot" type="riot/tag"></script>
<script src="tags/tag-history.riot" type="riot/tag"></script>
<script src="tags/tag-history-element.riot" type="riot/tag"></script>
<script src="tags/taglist.riot" type="riot/tag"></script>
<script src="tags/image-tag.riot" type="riot/tag"></script>
<script src="tags/remove-image.riot" type="riot/tag"></script>
<script src="tags/copy-to-clipboard.riot" type="riot/tag"></script>
<script src="tags/dialogs/add.riot" type="riot/tag"></script>
<script src="tags/dialogs/change.riot" type="riot/tag"></script>
<script src="tags/dialogs/remove.riot" type="riot/tag"></script>
<script src="tags/dialogs/menu.riot" type="riot/tag"></script>
<script src="tags/image-size.riot" type="riot/tag"></script>
<script src="tags/image-date.riot" type="riot/tag"></script>
<script src="tags/image-content-digest.riot" type="riot/tag"></script>
<script src="tags/pagination.riot" type="riot/tag"></script>
<script src="tags/app.riot" type="riot/tag"></script>
<script src="scripts/http.js"></script>
<script src="scripts/script.js"></script>
<script src="scripts/utils.js"></script>
<!-- endbuild -->
</body>
</html>

View file

@ -76,7 +76,10 @@ export function getHistoryIcon(attribute) {
case 'ExposedPorts':
return 'router';
default:
'';
if (attribute.startsWith('custom-label-')) {
return 'label';
}
return '';
}
}
@ -201,3 +204,7 @@ export function decodeURI(url) {
export function truthy(value) {
return value === true || value === 'true';
}
export function stringToArray(value) {
return value && typeof value === 'string' ? value.split(',') : [];
}