diff --git a/README.md b/README.md
index 130a79e8..0018496b 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,7 @@ Some env options are available for use this interface for **only one server**.
- `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.
+- `USE_CONTROL_CACHE_HEADER`: Use `Control-Cache` header and set to `no-store, no-cache`. This will avoid some issues on multi-arch images (see [#260](https://github.com/Joxit/docker-registry-ui/issues/260)). This option requires registry configuration: `Access-Control-Allow-Headers` with `Cache-Control`. (default: `false`).
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/).
@@ -128,7 +129,7 @@ http:
headers:
Access-Control-Allow-Origin: ['http://registry.example.com']
Access-Control-Allow-Credentials: [true]
- Access-Control-Allow-Headers: ['Authorization', 'Accept']
+ Access-Control-Allow-Headers: ['Authorization', 'Accept', 'Cache-Control']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional
```
@@ -150,7 +151,7 @@ And you need to add these HEADERS:
http:
headers:
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
- Access-Control-Allow-Headers: ['Authorization', 'Accept']
+ Access-Control-Allow-Headers: ['Authorization', 'Accept', 'Cache-Control']
Access-Control-Expose-Headers: ['Docker-Content-Digest']
```
@@ -178,7 +179,7 @@ http:
X-Content-Type-Options: [nosniff]
Access-Control-Allow-Origin: ['http://127.0.0.1:8000']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
- Access-Control-Allow-Headers: ['Authorization', 'Accept']
+ Access-Control-Allow-Headers: ['Authorization', 'Accept', 'Cache-Control']
Access-Control-Max-Age: [1728000]
Access-Control-Allow-Credentials: [true]
Access-Control-Expose-Headers: ['Docker-Content-Digest']
diff --git a/bin/90-docker-registry-ui.sh b/bin/90-docker-registry-ui.sh
index f8c9cf34..0f912062 100755
--- a/bin/90-docker-registry-ui.sh
+++ b/bin/90-docker-registry-ui.sh
@@ -10,6 +10,7 @@ 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
+sed -i "s~\${USE_CONTROL_CACHE_HEADER}~${USE_CONTROL_CACHE_HEADER}~" index.html
if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
sed -i "s/\${DELETE_IMAGES}/false/" index.html
diff --git a/src/components/docker-registry-ui.riot b/src/components/docker-registry-ui.riot
index 2878922e..eb6a0ce2 100644
--- a/src/components/docker-registry-ui.riot
+++ b/src/components/docker-registry-ui.riot
@@ -52,6 +52,7 @@ along with this program. If not, see .
on-notify="{ notifySnackbar }"
filter-results="{ state.filter }"
on-authentication="{ onAuthentication }"
+ use-control-cache-header="{ truthy(props.useControlCacheHeader) }"
>
@@ -65,6 +66,7 @@ along with this program. If not, see .
on-notify="{ notifySnackbar }"
on-authentication="{ onAuthentication }"
history-custom-labels="{ stringToArray(props.historyCustomLabels) }"
+ use-control-cache-header="{ truthy(props.useControlCacheHeader) }"
>
@@ -133,6 +135,7 @@ along with this program. If not, see .
this.state.name = props.name || stripHttps(props.registryUrl);
this.state.catalogElementsLimit = props.catalogElementsLimit || 100000;
this.state.pullUrl = this.pullUrl(this.state.registryUrl, props.pullUrl);
+ this.state.useControlCacheHeader = props.useControlCacheHeader;
},
onServerChange(registryUrl) {
this.update({
diff --git a/src/components/tag-history/tag-history.riot b/src/components/tag-history/tag-history.riot
index 32e76d91..52a9ddd7 100644
--- a/src/components/tag-history/tag-history.riot
+++ b/src/components/tag-history/tag-history.riot
@@ -57,6 +57,7 @@ along with this program. If not, see .
registryUrl: props.registryUrl,
onNotify: props.onNotify,
onAuthentication: props.onAuthentication,
+ useControlCacheHeader: props.useControlCacheHeader,
});
state.image.fillInfo();
},
@@ -66,7 +67,7 @@ along with this program. If not, see .
},
onTabChanged(arch, idx) {
const state = this.state;
- const { registryUrl, onNotify } = this.props;
+ const { registryUrl, onNotify, useControlCacheHeader } = this.props;
state.elements = [];
state.image.variants[idx] =
state.image.variants[idx] ||
@@ -74,6 +75,7 @@ along with this program. If not, see .
list: false,
registryUrl,
onNotify,
+ useControlCacheHeader,
});
if (state.image.variants[idx].blobs) {
return this.processBlobs(state.image.variants[idx].blobs);
diff --git a/src/components/tag-list/tag-list.riot b/src/components/tag-list/tag-list.riot
index 437a90bb..354e7ee1 100644
--- a/src/components/tag-list/tag-list.riot
+++ b/src/components/tag-list/tag-list.riot
@@ -95,6 +95,7 @@ along with this program. If not, see .
registryUrl: props.registryUrl,
onNotify: props.onNotify,
onAuthentication: props.onAuthentication,
+ useControlCacheHeader: props.useControlCacheHeader,
})
)
.sort(compare);
diff --git a/src/index.html b/src/index.html
index d8364297..69ddeb7d 100644
--- a/src/index.html
+++ b/src/index.html
@@ -46,6 +46,7 @@
read-only-registries="${READ_ONLY_REGISTRIES}"
show-catalog-nb-tags="${SHOW_CATALOG_NB_TAGS}"
history-custom-labels="${HISTORY_CUSTOM_LABELS}"
+ use-control-cache-header="${USE_CONTROL_CACHE_HEADER}"
>
@@ -60,6 +61,7 @@
single-registry="false"
show-catalog-nb-tags="true"
history-custom-labels="first_custom_labels,second_custom_labels"
+ use-control-cache-header="false"
>
diff --git a/src/scripts/docker-image.js b/src/scripts/docker-image.js
index 0a2f2ec5..74df9b52 100644
--- a/src/scripts/docker-image.js
+++ b/src/scripts/docker-image.js
@@ -46,7 +46,7 @@ export function compare(e1, e2) {
}
export class DockerImage {
- constructor(name, tag, { list, registryUrl, onNotify, onAuthentication }) {
+ constructor(name, tag, { list, registryUrl, onNotify, onAuthentication, useControlCacheHeader }) {
this.name = name;
this.tag = tag;
this.chars = 0;
@@ -55,6 +55,7 @@ export class DockerImage {
registryUrl,
onNotify,
onAuthentication,
+ useControlCacheHeader,
};
this.ociImage = false;
observable(this);
@@ -143,6 +144,9 @@ export class DockerImage {
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json' +
(self.opts.list ? ', application/vnd.docker.distribution.manifest.list.v2+json' : '')
);
+ if (self.opts.useControlCacheHeader) {
+ oReq.setRequestHeader('Cache-Control', 'no-store, no-cache');
+ }
oReq.send();
}
getBlobs(blob) {
@@ -165,7 +169,9 @@ export class DockerImage {
self.trigger('creation-date', self.creationDate);
self.trigger('blobs', self.blobs);
} else if (this.status === 404) {
- self.opts.onNotify(`Blobs for ${self.name}:${self.tag} not found`, true);
+ self.opts.onNotify(`Blobs for ${self.name}:${self.tag} not found: blob '${self.blobs}'`, true);
+ } else if (!this.responseText) {
+ self.opts.onNotify(`Can"t get blobs for ${self.name}:${self.tag}: blob '${self.blobs}' (no message error)`, true);
} else {
self.opts.onNotify(this.responseText);
}