Skip to content

Commit affb057

Browse files
authored
feat(catalog): add multi level branching in catalog (#319)
Multi level branching with `CATALOG_MIN_BRANCHES` and `CATALOG_MAX_BRANCHES`
2 parents d2e6cdc + dbfc9fe commit affb057

File tree

12 files changed

+259
-40
lines changed

12 files changed

+259
-40
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ Some env options are available for use this interface for **only one server** (w
100100
- `THEME_*`: See table in [Theme options](#theme-options) section (see [#283](https://github.com/Joxit/docker-registry-ui/pull/283)). Since 2.4.0
101101
- `TAGLIST_ORDER`: Set the default order for the taglist page, could be `num-asc;alpha-asc`, `num-desc;alpha-asc`, `num-asc;alpha-desc`, `num-desc;alpha-desc`, `alpha-asc;num-asc`, `alpha-asc;num-desc`, `alpha-desc;num-asc` or `alpha-desc;num-desc` (see [#307](https://github.com/Joxit/docker-registry-ui/pull/307)). (default: `alpha-asc;num-desc`). Since 2.5.0
102102
- `CATALOG_DEFAULT_EXPANDED`: Expand by default all repositories in catalog (see [#302](https://github.com/Joxit/docker-registry-ui/issues/302)). (default: `false`). Since 2.5.0
103+
- `CATALOG_MIN_BRANCHES`: Set the minimum repository/namespace to expand (e.g. `joxit/docker-registry-ui` `joxit/` is the repository/namespace). Can be 0 to disable branching. (see [#319](https://github.com/Joxit/docker-registry-ui/pull/319)). (default: `1`). Since 2.5.0
104+
- `CATALOG_MAX_BRANCHES`: Set the maximum repository/namespace to expand (e.g. `joxit/docker-registry-ui` `joxit/` is the repository/namespace). Can be 0 to disable branching. (see [#319](https://github.com/Joxit/docker-registry-ui/pull/319)). (default: `1`). Since 2.5.0
103105

104106
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/).
105107

bin/90-docker-registry-ui.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ sed -i "s~\${HISTORY_CUSTOM_LABELS}~${HISTORY_CUSTOM_LABELS}~" index.html
1313
sed -i "s~\${USE_CONTROL_CACHE_HEADER}~${USE_CONTROL_CACHE_HEADER}~" index.html
1414
sed -i "s~\${TAGLIST_ORDER}~${TAGLIST_ORDER}~" index.html
1515
sed -i "s~\${CATALOG_DEFAULT_EXPANDED}~${CATALOG_DEFAULT_EXPANDED}~" index.html
16+
sed -i "s~\${CATALOG_MIN_BRANCHES}~${CATALOG_MIN_BRANCHES}~" index.html
17+
sed -i "s~\${CATALOG_MAX_BRANCHES}~${CATALOG_MAX_BRANCHES}~" index.html
1618

1719
grep -o 'THEME[A-Z_]*' index.html | while read e; do
1820
sed -i "s~\${$e}~$(printenv $e)~" index.html

src/components/catalog/catalog-element.riot

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
5252
show-catalog-nb-tags="{ props.showCatalogNbTags }"
5353
class="animated {!state.expanded && !props.filterResults ? 'hide' : ''} {state.expanding ? 'expanding' : ''}"
5454
each="{item in state.images}"
55+
z-index="{ props.zIndex - 1 }"
5556
item="{ item }"
5657
></catalog-element>
5758
</div>
@@ -76,6 +77,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
7677
this.getNbTags(props, state);
7778
}
7879
},
80+
onMounted(props, state) {
81+
const materialCard = this.$('material-card');
82+
if (materialCard) {
83+
materialCard.style['z-index'] = props.zIndex;
84+
}
85+
},
7986
onBeforeUpdate(props, state) {
8087
if (props.filterResults && state.images) {
8188
state.nImages = state.images.filter((image) => matchSearch(props.filterResults, image)).length;

src/components/catalog/catalog.riot

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
3535
on-authentication="{ props.onAuthentication }"
3636
show-catalog-nb-tags="{ props.showCatalogNbTags }"
3737
catalog-default-expanded="{ props.catalogDefaultExpanded || state.nRepositories === 1 }"
38+
z-index="{ props.catalogMaxBranches - props.catalogMinBranches + 2 }"
3839
></catalog-element>
3940
<script>
4041
import CatalogElement from './catalog-element.riot';
4142
import { Http } from '../../scripts/http';
43+
import { getBranching } from '../../scripts/repositories';
4244
import { getRegistryServers } from '../../scripts/utils';
4345
4446
export default {
@@ -56,6 +58,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
5658
onBeforeMount(props) {
5759
this.state.registryName = props.registryName;
5860
this.state.catalogElementsLimit = props.catalogElementsLimit;
61+
try {
62+
this.state.branching = getBranching(props.catalogMinBranches, props.catalogMaxBranches);
63+
} catch (e) {
64+
props.onNotify(e);
65+
}
5966
},
6067
onMounted(props, state) {
6168
this.display(props, state);
@@ -69,6 +76,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
6976
}
7077
state.registryUrl = props.registryUrl;
7178
let repositories = [];
79+
let nImages = 0;
7280
const self = this;
7381
const catalogUrl = `${props.registryUrl}/v2/_catalog?n=${state.catalogElementsLimit}`;
7482
const oReq = new Http({
@@ -78,22 +86,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
7886
if (this.status === 200) {
7987
repositories = JSON.parse(this.responseText).repositories || [];
8088
repositories.sort();
81-
repositories = repositories.reduce(function (acc, e) {
82-
const slash = e.indexOf('/');
83-
if (slash > 0) {
84-
const repoName = e.substring(0, slash) + '/';
85-
if (acc.length === 0 || acc[acc.length - 1].repo != repoName) {
86-
acc.push({
87-
repo: repoName,
88-
images: [],
89-
});
90-
}
91-
acc[acc.length - 1].images.push(e);
92-
return acc;
93-
}
94-
acc.push(e);
95-
return acc;
96-
}, []);
89+
nImages = repositories.length;
90+
if (typeof state.branching === 'function') {
91+
repositories = state.branching(repositories);
92+
}
9793
} else if (this.status === 404) {
9894
self.props.onNotify({ code: 'CATALOG_NOT_FOUND', url: catalogUrl }, true);
9995
} else if (this.status === 400) {
@@ -116,7 +112,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
116112
self.update({
117113
repositories,
118114
nRepositories: repositories.length,
119-
nImages: repositories.reduce((acc, e) => acc + ((e.images && e.images.length) || 1), 0),
115+
nImages,
120116
loadend: true,
121117
});
122118
});
@@ -125,4 +121,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
125121
},
126122
};
127123
</script>
124+
<style>
125+
catalog {
126+
display: block;
127+
margin: auto;
128+
}
129+
catalog > material-card {
130+
width: 100%;
131+
}
132+
</style>
128133
</catalog>

src/components/docker-registry-ui.riot

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
3131
</material-navbar>
3232
</header>
3333
<main>
34+
<error-page
35+
if="{ state.pageError && !Array.isArray(state.pageError.errors) }"
36+
code="{ state.pageError.code }"
37+
status="{ state.pageError.status }"
38+
message="{ state.pageError.message }"
39+
url="{ state.pageError.url }"
40+
></error-page>
41+
<error-page
42+
if="{ state.pageError && Array.isArray(state.pageError.errors) }"
43+
each="{ error in (state.pageError && state.pageError.errors) }"
44+
code="{ error.code }"
45+
detail="{ error.detail }"
46+
message="{ error.message }"
47+
url="{ state.pageError.url }"
48+
></error-page>
3449
<router base="#!">
3550
<route path="{baseRoute}">
3651
<catalog
@@ -42,6 +57,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
4257
on-authentication="{ onAuthentication }"
4358
show-catalog-nb-tags="{ truthy(props.showCatalogNbTags) }"
4459
catalog-default-expanded="{ truthy(props.catalogDefaultExpanded) }"
60+
catalog-min-branches="{ props.catalogMinBranches }"
61+
catalog-max-branches="{ props.catalogMaxBranches }"
4562
></catalog>
4663
</route>
4764
<route path="{baseRoute}taglist/(.*)">
@@ -82,21 +99,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
8299
on-authenticated="{ state.onAuthenticated }"
83100
opened="{ state.authenticationDialogOpened }"
84101
></registry-authentication>
85-
<error-page
86-
if="{ state.pageError && !Array.isArray(state.pageError.errors) }"
87-
code="{ state.pageError.code }"
88-
status="{ state.pageError.status }"
89-
message="{ state.pageError.message }"
90-
url="{ state.pageError.url }"
91-
></error-page>
92-
<error-page
93-
if="{ state.pageError && Array.isArray(state.pageError.errors) }"
94-
each="{ error in (state.pageError && state.pageError.errors) }"
95-
code="{ error.code }"
96-
detail="{ error.detail }"
97-
message="{ error.message }"
98-
url="{ state.pageError.url }"
99-
></error-page>
100102
<material-snackbar message="{ state.snackbarMessage }" is-error="{ state.snackbarIsError }"></material-snackbar>
101103
</main>
102104
<footer>

src/components/error-page.riot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
<a href="https://github.com/Joxit/docker-registry-ui/issues/306">Joxit/docker-registry-ui#306</a>.
6262
</p>
6363
</template>
64+
<template if="{ props.code === 'CATALOG_BRANCHING_CONFIGURATION' }">
65+
<p>Wrong configuration for the branching feature: { props.message }</p>
66+
<p>Configuration environment variables are : CATALOG_MIN_BRANCH and CATALOG_MAX_BRANCH</p>
67+
</template>
6468
</div>
6569
<script>
6670
export default {

src/components/tag-history/tag-history.riot

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
159159
return router.taglist(this.props.image);
160160
},
161161
showDockerfile() {
162-
console.log(this);
163162
this.update({ showDockerfile: true });
164163
},
165164
onDockerfileClose() {

src/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
use-control-cache-header="${USE_CONTROL_CACHE_HEADER}"
5050
taglist-order="${TAGLIST_ORDER}"
5151
catalog-default-expanded="${CATALOG_DEFAULT_EXPANDED}"
52+
catalog-min-branches="${CATALOG_MIN_BRANCHES}"
53+
catalog-max-branches="${CATALOG_MAX_BRANCHES}"
5254
theme="${THEME}"
5355
theme-primary-text="${THEME_PRIMARY_TEXT}"
5456
theme-neutral-text="${THEME_NEUTRAL_TEXT}"
@@ -77,6 +79,8 @@
7779
use-control-cache-header="false"
7880
taglist-order=""
7981
catalog-default-expanded=""
82+
catalog-min-branches="1"
83+
catalog-max-branches="1"
8084
theme="auto"
8185
theme-primary-text=""
8286
theme-neutral-text=""

src/scripts/error.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export class DockerRegistryUIError extends Error {
2-
constructor(msg) {
2+
constructor(msg, code) {
33
super(msg);
44
this.isError = true;
5+
this.code = code;
56
}
67
}

src/scripts/repositories.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { DockerRegistryUIError } from './error.js';
2+
const ERROR_CODE = 'CATALOG_BRANCHING_CONFIGURATION';
3+
4+
const getRepositoryName = (split, max) => {
5+
let repositoryName = '';
6+
for (let i = 0; i < Math.min(max, split.length - 1); i++) {
7+
repositoryName += `${split[i]}/`;
8+
}
9+
return repositoryName;
10+
};
11+
12+
const getLatestRepository = (repo, repoName) => {
13+
if (!repo.images) {
14+
return;
15+
}
16+
if (repo.repo === repoName) {
17+
return repo;
18+
}
19+
for (let i = 0; i < repo.images.length; i++) {
20+
const res = getLatestRepository(repo.images[i], repoName);
21+
if (res) {
22+
return res;
23+
}
24+
}
25+
26+
if (repoName.startsWith(repo.repo)) {
27+
const newRepo = { repo: repoName, images: [] };
28+
repo.images.push(newRepo);
29+
return newRepo;
30+
}
31+
};
32+
33+
const cleanInt = (n) => (n === '' ? 1 : parseInt(n));
34+
35+
export const getBranching = (min = 1, max = 1) => {
36+
min = cleanInt(min);
37+
max = cleanInt(max);
38+
if (isNaN(min) || isNaN(max)) {
39+
throw new DockerRegistryUIError(`min and max must be integers: (min: ${min} and max: ${max}))`, ERROR_CODE);
40+
} else if (min > max) {
41+
throw new DockerRegistryUIError(`min must be inferior to max (min: ${min} <= max: ${max})`, ERROR_CODE);
42+
} else if (max < 0 || min < 0) {
43+
throw new DockerRegistryUIError(
44+
`min and max must be greater than equals to 0 (min: ${min} >= 0 and max: ${max} >= 0)`,
45+
ERROR_CODE
46+
);
47+
}
48+
if (max == 1) {
49+
min = 1;
50+
}
51+
return (repositories) =>
52+
repositories.sort().reduce(function (acc, image) {
53+
const split = image.split('/');
54+
if (split.length > min && min > 0) {
55+
const repoName = getRepositoryName(split, max);
56+
let repo = acc.length > 0 && getLatestRepository(acc[acc.length - 1], repoName);
57+
if (!repo) {
58+
repo = {
59+
repo: repoName,
60+
images: [],
61+
};
62+
acc.push(repo);
63+
}
64+
repo.images.push(image);
65+
return acc;
66+
}
67+
acc.push(image);
68+
return acc;
69+
}, []);
70+
};

0 commit comments

Comments
 (0)