feat(branching): add configuration for catalog arborescence

This commit is contained in:
Joxit 2023-05-30 07:49:18 +02:00
parent 398fa65fa1
commit 1031034bc4
No known key found for this signature in database
GPG Key ID: F526592B8E012263
11 changed files with 80 additions and 43 deletions

View File

@ -13,6 +13,8 @@ sed -i "s~\${HISTORY_CUSTOM_LABELS}~${HISTORY_CUSTOM_LABELS}~" index.html
sed -i "s~\${USE_CONTROL_CACHE_HEADER}~${USE_CONTROL_CACHE_HEADER}~" index.html sed -i "s~\${USE_CONTROL_CACHE_HEADER}~${USE_CONTROL_CACHE_HEADER}~" index.html
sed -i "s~\${TAGLIST_ORDER}~${TAGLIST_ORDER}~" index.html sed -i "s~\${TAGLIST_ORDER}~${TAGLIST_ORDER}~" index.html
sed -i "s~\${CATALOG_DEFAULT_EXPANDED}~${CATALOG_DEFAULT_EXPANDED}~" index.html sed -i "s~\${CATALOG_DEFAULT_EXPANDED}~${CATALOG_DEFAULT_EXPANDED}~" index.html
sed -i "s~\${CATALOG_MIN_BRANCHES}~${CATALOG_MIN_BRANCHES}~" index.html
sed -i "s~\${CATALOG_MAX_BRANCHES}~${CATALOG_MAX_BRANCHES}~" index.html
grep -o 'THEME[A-Z_]*' index.html | while read e; do grep -o 'THEME[A-Z_]*' index.html | while read e; do
sed -i "s~\${$e}~$(printenv $e)~" index.html sed -i "s~\${$e}~$(printenv $e)~" index.html

View File

@ -52,6 +52,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
show-catalog-nb-tags="{ props.showCatalogNbTags }" show-catalog-nb-tags="{ props.showCatalogNbTags }"
class="animated {!state.expanded && !props.filterResults ? 'hide' : ''} {state.expanding ? 'expanding' : ''}" class="animated {!state.expanded && !props.filterResults ? 'hide' : ''} {state.expanding ? 'expanding' : ''}"
each="{item in state.images}" each="{item in state.images}"
z-index="{ props.zIndex - 1 }"
item="{ item }" item="{ item }"
></catalog-element> ></catalog-element>
</div> </div>
@ -76,6 +77,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
this.getNbTags(props, state); this.getNbTags(props, state);
} }
}, },
onMounted(props, state) {
const materialCard = this.$('material-card');
if (materialCard) {
materialCard.style['z-index'] = props.zIndex;
}
},
onBeforeUpdate(props, state) { onBeforeUpdate(props, state) {
if (props.filterResults && state.images) { if (props.filterResults && state.images) {
state.nImages = state.images.filter((image) => matchSearch(props.filterResults, image)).length; state.nImages = state.images.filter((image) => matchSearch(props.filterResults, image)).length;

View File

@ -35,10 +35,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-authentication="{ props.onAuthentication }" on-authentication="{ props.onAuthentication }"
show-catalog-nb-tags="{ props.showCatalogNbTags }" show-catalog-nb-tags="{ props.showCatalogNbTags }"
catalog-default-expanded="{ props.catalogDefaultExpanded || state.nRepositories === 1 }" catalog-default-expanded="{ props.catalogDefaultExpanded || state.nRepositories === 1 }"
z-index="{ props.catalogMaxBranches - props.catalogMinBranches + 2 }"
></catalog-element> ></catalog-element>
<script> <script>
import CatalogElement from './catalog-element.riot'; import CatalogElement from './catalog-element.riot';
import { Http } from '../../scripts/http'; import { Http } from '../../scripts/http';
import { getBranching } from '../../scripts/repositories';
import { getRegistryServers } from '../../scripts/utils'; import { getRegistryServers } from '../../scripts/utils';
export default { export default {
@ -56,6 +58,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
onBeforeMount(props) { onBeforeMount(props) {
this.state.registryName = props.registryName; this.state.registryName = props.registryName;
this.state.catalogElementsLimit = props.catalogElementsLimit; this.state.catalogElementsLimit = props.catalogElementsLimit;
try {
this.state.branching = getBranching(props.catalogMinBranches, props.catalogMaxBranches);
} catch (e) {
props.onNotify(e);
}
}, },
onMounted(props, state) { onMounted(props, state) {
this.display(props, state); this.display(props, state);
@ -69,6 +76,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
} }
state.registryUrl = props.registryUrl; state.registryUrl = props.registryUrl;
let repositories = []; let repositories = [];
let nImages = 0;
const self = this; const self = this;
const catalogUrl = `${props.registryUrl}/v2/_catalog?n=${state.catalogElementsLimit}`; const catalogUrl = `${props.registryUrl}/v2/_catalog?n=${state.catalogElementsLimit}`;
const oReq = new Http({ const oReq = new Http({
@ -78,22 +86,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
if (this.status === 200) { if (this.status === 200) {
repositories = JSON.parse(this.responseText).repositories || []; repositories = JSON.parse(this.responseText).repositories || [];
repositories.sort(); repositories.sort();
repositories = repositories.reduce(function (acc, e) { nImages = repositories.length;
const slash = e.indexOf('/'); if (typeof state.branching === 'function') {
if (slash > 0) { repositories = state.branching(repositories);
const repoName = e.substring(0, slash) + '/'; }
if (acc.length === 0 || acc[acc.length - 1].repo != repoName) {
acc.push({
repo: repoName,
images: [],
});
}
acc[acc.length - 1].images.push(e);
return acc;
}
acc.push(e);
return acc;
}, []);
} else if (this.status === 404) { } else if (this.status === 404) {
self.props.onNotify({ code: 'CATALOG_NOT_FOUND', url: catalogUrl }, true); self.props.onNotify({ code: 'CATALOG_NOT_FOUND', url: catalogUrl }, true);
} else if (this.status === 400) { } else if (this.status === 400) {
@ -116,7 +112,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
self.update({ self.update({
repositories, repositories,
nRepositories: repositories.length, nRepositories: repositories.length,
nImages: repositories.reduce((acc, e) => acc + ((e.images && e.images.length) || 1), 0), nImages,
loadend: true, loadend: true,
}); });
}); });
@ -125,4 +121,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}, },
}; };
</script> </script>
<style>
catalog {
display: block;
margin: auto;
}
catalog > material-card {
width: 100%;
}
</style>
</catalog> </catalog>

View File

@ -31,6 +31,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</material-navbar> </material-navbar>
</header> </header>
<main> <main>
<error-page
if="{ state.pageError && !Array.isArray(state.pageError.errors) }"
code="{ state.pageError.code }"
status="{ state.pageError.status }"
message="{ state.pageError.message }"
url="{ state.pageError.url }"
></error-page>
<error-page
if="{ state.pageError && Array.isArray(state.pageError.errors) }"
each="{ error in (state.pageError && state.pageError.errors) }"
code="{ error.code }"
detail="{ error.detail }"
message="{ error.message }"
url="{ state.pageError.url }"
></error-page>
<router base="#!"> <router base="#!">
<route path="{baseRoute}"> <route path="{baseRoute}">
<catalog <catalog
@ -42,6 +57,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-authentication="{ onAuthentication }" on-authentication="{ onAuthentication }"
show-catalog-nb-tags="{ truthy(props.showCatalogNbTags) }" show-catalog-nb-tags="{ truthy(props.showCatalogNbTags) }"
catalog-default-expanded="{ truthy(props.catalogDefaultExpanded) }" catalog-default-expanded="{ truthy(props.catalogDefaultExpanded) }"
catalog-min-branches="{ props.catalogMinBranches }"
catalog-max-branches="{ props.catalogMaxBranches }"
></catalog> ></catalog>
</route> </route>
<route path="{baseRoute}taglist/(.*)"> <route path="{baseRoute}taglist/(.*)">
@ -82,21 +99,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-authenticated="{ state.onAuthenticated }" on-authenticated="{ state.onAuthenticated }"
opened="{ state.authenticationDialogOpened }" opened="{ state.authenticationDialogOpened }"
></registry-authentication> ></registry-authentication>
<error-page
if="{ state.pageError && !Array.isArray(state.pageError.errors) }"
code="{ state.pageError.code }"
status="{ state.pageError.status }"
message="{ state.pageError.message }"
url="{ state.pageError.url }"
></error-page>
<error-page
if="{ state.pageError && Array.isArray(state.pageError.errors) }"
each="{ error in (state.pageError && state.pageError.errors) }"
code="{ error.code }"
detail="{ error.detail }"
message="{ error.message }"
url="{ state.pageError.url }"
></error-page>
<material-snackbar message="{ state.snackbarMessage }" is-error="{ state.snackbarIsError }"></material-snackbar> <material-snackbar message="{ state.snackbarMessage }" is-error="{ state.snackbarIsError }"></material-snackbar>
</main> </main>
<footer> <footer>

View File

@ -61,6 +61,10 @@
<a href="https://github.com/Joxit/docker-registry-ui/issues/306">Joxit/docker-registry-ui#306</a>. <a href="https://github.com/Joxit/docker-registry-ui/issues/306">Joxit/docker-registry-ui#306</a>.
</p> </p>
</template> </template>
<template if="{ props.code === 'CATALOG_BRANCHING_CONFIGURATION' }">
<p>Wrong configuration for the branching feature: { props.message }</p>
<p>Configuration environment variables are : CATALOG_MIN_BRANCH and CATALOG_MAX_BRANCH</p>
</template>
</div> </div>
<script> <script>
export default { export default {

View File

@ -159,7 +159,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return router.taglist(this.props.image); return router.taglist(this.props.image);
}, },
showDockerfile() { showDockerfile() {
console.log(this);
this.update({ showDockerfile: true }); this.update({ showDockerfile: true });
}, },
onDockerfileClose() { onDockerfileClose() {

View File

@ -49,6 +49,8 @@
use-control-cache-header="${USE_CONTROL_CACHE_HEADER}" use-control-cache-header="${USE_CONTROL_CACHE_HEADER}"
taglist-order="${TAGLIST_ORDER}" taglist-order="${TAGLIST_ORDER}"
catalog-default-expanded="${CATALOG_DEFAULT_EXPANDED}" catalog-default-expanded="${CATALOG_DEFAULT_EXPANDED}"
catalog-min-branches="${CATALOG_MIN_BRANCHES}"
catalog-max-branches="${CATALOG_MAX_BRANCHES}"
theme="${THEME}" theme="${THEME}"
theme-primary-text="${THEME_PRIMARY_TEXT}" theme-primary-text="${THEME_PRIMARY_TEXT}"
theme-neutral-text="${THEME_NEUTRAL_TEXT}" theme-neutral-text="${THEME_NEUTRAL_TEXT}"
@ -77,6 +79,8 @@
use-control-cache-header="false" use-control-cache-header="false"
taglist-order="" taglist-order=""
catalog-default-expanded="" catalog-default-expanded=""
catalog-min-branches="1"
catalog-max-branches="1"
theme="auto" theme="auto"
theme-primary-text="" theme-primary-text=""
theme-neutral-text="" theme-neutral-text=""

View File

@ -1,6 +1,7 @@
export class DockerRegistryUIError extends Error { export class DockerRegistryUIError extends Error {
constructor(msg) { constructor(msg, code) {
super(msg); super(msg);
this.isError = true; this.isError = true;
this.code = code;
} }
} }

View File

@ -1,4 +1,5 @@
import { DockerRegistryUIError } from './error.js'; import { DockerRegistryUIError } from './error.js';
const ERROR_CODE = 'CATALOG_BRANCHING_CONFIGURATION';
const getRepositoryName = (split, max) => { const getRepositoryName = (split, max) => {
let repositoryName = ''; let repositoryName = '';
@ -29,14 +30,24 @@ const getLatestRepository = (repo, repoName) => {
} }
}; };
const cleanInt = (n) => (n === '' ? 1 : parseInt(n));
export const getBranching = (min = 1, max = 1) => { export const getBranching = (min = 1, max = 1) => {
if (min > max) { min = cleanInt(min);
throw new DockerRegistryUIError(`min must be inferior to max (min: ${min} <= max: ${max})`); max = cleanInt(max);
if (isNaN(min) || isNaN(max)) {
throw new DockerRegistryUIError(`min and max must be integers: (min: ${min} and max: ${max}))`, ERROR_CODE);
} else if (min > max) {
throw new DockerRegistryUIError(`min must be inferior to max (min: ${min} <= max: ${max})`, ERROR_CODE);
} else if (max < 0 || min < 0) { } else if (max < 0 || min < 0) {
throw new DockerRegistryUIError( throw new DockerRegistryUIError(
`min and max must be greater than equals to 0 (min: ${min} >= 0 and max: ${max} >= 0)` `min and max must be greater than equals to 0 (min: ${min} >= 0 and max: ${max} >= 0)`,
ERROR_CODE
); );
} }
if (max == 1) {
min = 1;
}
return (repositories) => return (repositories) =>
repositories.sort().reduce(function (acc, image) { repositories.sort().reduce(function (acc, image) {
const split = image.split('/'); const split = image.split('/');

View File

@ -69,17 +69,18 @@ material-card {
material-card, material-card,
material-tabs, material-tabs,
pagination .container { pagination .container {
max-width: 95%;
margin: auto; margin: auto;
margin-top: 20px; margin-top: 20px;
margin-bottom: 20px; margin-bottom: 20px;
} }
/* 1515px * 0.95 = 1440px */ material-card,
@media screen and (min-width: 1515px) { material-tabs,
material-card, pagination .container,
material-tabs, catalog {
pagination .container { max-width: 95%;
/* 1515px * 0.95 = 1440px */
@media screen and (min-width: 1515px) {
max-width: 1440px; max-width: 1440px;
} }
} }

View File

@ -117,5 +117,6 @@ describe('repositories', () => {
assert.throws(() => getBranching(2, 1), DockerRegistryUIError, `Did not throw on min > max`); assert.throws(() => getBranching(2, 1), DockerRegistryUIError, `Did not throw on min > max`);
assert.throws(() => getBranching(-2, 1), DockerRegistryUIError, `Did not throw on min < 0`); assert.throws(() => getBranching(-2, 1), DockerRegistryUIError, `Did not throw on min < 0`);
assert.throws(() => getBranching(2, -1), DockerRegistryUIError, `Did not throw on max < 0`); assert.throws(() => getBranching(2, -1), DockerRegistryUIError, `Did not throw on max < 0`);
assert.throws(() => getBranching('foo', 'bar'), DockerRegistryUIError, `Did not throw on max < 0`);
}); });
}); });