Commit 51c9f8b6 authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] First iteration: port haml into vue

parent 23024a70
<script>
/* globals Flash */
import { mapGetters, mapActions } from 'vuex';
import '../../flash';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import store from '../stores';
import collapsibleContainer from './collapsible_container.vue';
import { errorMessages, errorMessagesTypes } from '../constants';
export default {
name: 'registryListApp',
props: {
endpoint: {
type: String,
required: true
},
},
store,
components: {
collapsibleContainer,
loadingIcon,
},
computed: {
...mapGetters([
'isLoading',
'repos',
]),
},
methods: {
...mapActions([
'setMainEndpoint',
'fetchRepos',
'fetchList',
'deleteRepo',
'deleteRegistry',
'toggleIsLoading',
]),
fetchRegistryList(repo) {
this.fetchList(repo)
.catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY))
},
deleteRegistry(repo, registry) {
this.deleteRegistry(registry)
.then(() => this.fetchRegistry(repo))
.catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY));
},
deleteRepository(repo) {
this.deleteRepo(repo)
.then(() => this.fetchRepo())
.catch(() => this.showError(errorMessagesTypes.DELETE_REPO));
},
showError(message){
Flash(__(errorMessages[message]));
}
},
created() {
this.setMainEndpoint(this.endpoint);
},
mounted() {
this.fetchRepos()
.catch(() => this.showError(errorMessagesTypes.FETCH_REPOS));
}
};
</script>
<template>
<div>
<loading-icon
v-if="isLoading"
size="3"
/>
<collapsible-container
v-else-if="!isLoading && repos.length"
v-for="(item, index) in repos"
:key="index"
:repo="item"
@fetchRegistryList="fetchRegistryList"
@deleteRepository="deleteRepository"
@deleteRegistry="deleteRegistry"
/>
<p v-else-if="!isLoading && !repos.length">
{{__("No container images stored for this project. Add one by following the instructions above")}}
</p>
</div>
</template>
<script> <script>
import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
name: 'collapsibeContainerRegisty', name: 'collapsibeContainerRegisty',
props: { props: {
title: { repo: {
type: String,
required: true,
},
clipboardContent: {
type: String,
required: true,
},
repoData: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
components: { components: {
clipboardButton, clipboardButton,
loadingIcon,
},
directives: {
tooltip,
}, },
data() { data() {
return { return {
...@@ -26,37 +24,73 @@ ...@@ -26,37 +24,73 @@
}; };
}, },
methods: { methods: {
itemSize(item) { layers(item) {
const pluralize = gl.text.pluralize('layer', item.layers); const pluralize = gl.text.pluralize('layer', item.layers);
return `${item.size}&middot;${item.layers}${pluralize}`; return `${item.layers} ${pluralize}`;
} },
} toggleRepo() {
if (this.isOpen === false) {
// consider not fetching data the second time it is toggled? :fry:
this.$emit('fetchRegistryList', this.repo);
} }
this.isOpen = !this.isOpen;
},
handleDeleteRepository() {
this.$emit('deleteRepository', this.repo)
},
handleDeleteRegistry(registry) {
this.$emit('deleteRegistry', this.repo, registry);
},
},
};
</script> </script>
<template> <template>
<div class="container-image"> <div class="container-image">
<div class="container-image-head"> <div
class="container-image-head">
<a
role="button"
@click="toggleRepo">
<i <i
class="fa" class="fa"
:class="{ :class="{
'chevron-left': !isOpen, 'fa-chevron-right': !isOpen,
'chevron-up': isOpen, 'fa-chevron-up': isOpen,
}" }"
aria-hidden="true"> aria-hidden="true">
</i> </i>
{{title}} {{repo.name}}
</a>
<clipboard-button text="foo" title="bar" />
<div class="controls hidden-xs pull-right">
<button
v-if="repo.canDelete"
type="button"
class="btn btn-remove"
:title="__('Remove repository')"
v-tooltip
@click="handleDeleteRepository">
<i
class="fa fa-trash"
aria-hidden="true">
</i>
</button>
</div>
<clipboard-button
:text=""
:title=""
/>
</div> </div>
<loading-icon
v-if="repo.isLoading"
/>
<div <div
class="container-image-tags" v-else-if="!repo.isLoading && isOpen"
:class="{ hide: !isOpen }"> class="container-image-tags">
<table class="table tags" v-if="true"> <table class="table tags" v-if="repo.list.length">
<thead> <thead>
<tr> <tr>
<th>{{__("Tag")}}</th> <th>{{__("Tag")}}</th>
...@@ -71,23 +105,28 @@ ...@@ -71,23 +105,28 @@
v-for="(item, i) in repo.list" v-for="(item, i) in repo.list"
:key="i"> :key="i">
<td> <td>
{{item.name}}
{{item.tag}}
<clipboard-button <clipboard-button
:title="item.location" :title="item.tag"
:text="item.location" :text="item.tag"
/> />
</td> </td>
<td> <td>
<span <span
v-tooltip v-tooltip
:title="item.revision" :title="item.revision"
data-placement="bottom"
> >
{{item.shortRevision}} {{item.shortRevision}}
</span> </span>
</td> </td>
<td> <td>
<template v-if="item.size"> <template v-if="item.size">
{{itemSize(item)}} {{item.size}}
&middot;
{{layers(item)}}
</template> </template>
<div v-else class="light"> <div v-else class="light">
\- \-
...@@ -103,18 +142,20 @@ ...@@ -103,18 +142,20 @@
</div> </div>
</td> </td>
<td> <td class="content">
<div class="controls hidden-xs pull-right">
<button <button
type="button" type="button"
class="btn btn-remove" class="btn btn-remove"
title="Remove tag" title="Remove tag"
v-tooltip v-tooltip
@click="deleteTag(item)"> @click="handleDeleteRegistry(item)">
<i <i
class="fa fa-trash cred" class="fa fa-trash"
aria-hidden="true"> aria-hidden="true">
</i> </i>
</button> </button>
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
...@@ -123,9 +164,7 @@ ...@@ -123,9 +164,7 @@
v-else v-else
class="nothing-here-block"> class="nothing-here-block">
{{__("No tags in Container Registry for this container image.")}} {{__("No tags in Container Registry for this container image.")}}
</div> </div>
</div> </div>
</div> </div>
</template> </template>
export const errorMessagesTypes = {
FETCH_REGISTRY: 'FETCH_REGISTRY',
FETCH_REPOS: 'FETCH_REPOS',
DELETE_REPO: 'DELETE_REPO',
DELETE_REGISTRY: 'DELETE_REGISTRY',
};
export const errorMessages = {
[errorMessagesTypes.FETCH_REGISTRY]: 'Something went wrong while fetching the registry list.',
[errorMessagesTypes.FETCH_REPOS]: 'Something went wrong while fetching the repositories.',
[errorMessagesTypes.DELETE_REPO]: 'Something went wrong while deleting the repository.',
[errorMessagesTypes.DELETE_REGISTRY]: 'Something went wrong while deleting registry.',
};
import Vue from 'vue';
import Translate from '../vue_shared/translate';
import registryApp from './components/app.vue';
// Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-registry-images',
components: {
registryApp,
},
data() {
const dataset = document.querySelector(this.$options.el).dataset;
return {
endpoint: dataset.endpoint,
};
},
render(createElement) {
return createElement('registry-app', {
props: {
endpoint: this.endpoint,
},
});
},
}));
...@@ -16,13 +16,13 @@ export const fetchRepos = ({ commit, state }) => { ...@@ -16,13 +16,13 @@ export const fetchRepos = ({ commit, state }) => {
}; };
export const fetchList = ({ commit }, list) => { export const fetchList = ({ commit }, list) => {
commit(types.TOGGLE_IMAGE_LOADING, list); commit(types.TOGGLE_REGISTRY_LIST_LOADING, list);
return Vue.http.get(list.path) return Vue.http.get(list.path)
.then(res => res.json()) .then(res => res.json())
.then((response) => { .then((response) => {
commit(types.TOGGLE_IMAGE_LOADING, list); commit(types.TOGGLE_REGISTRY_LIST_LOADING, list);
commit(types.SET_IMAGES_LIST, list, response); commit(types.SET_REGISTRY_LIST, list, response);
}); });
}; };
...@@ -32,8 +32,11 @@ export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.path) ...@@ -32,8 +32,11 @@ export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.path)
commit(types.DELETE_REPO, repo); commit(types.DELETE_REPO, repo);
}); });
export const deleteImage = ({ commit }, image) => Vue.http.delete(image.path) export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.path)
.then(res => res.json()) .then(res => res.json())
.then(() => { .then(() => {
commit(types.DELETE_IMAGE, image); commit(types.DELETE_IMAGE, image);
}); });
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleIsLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
export const isLoading = state => state.isLoading;
export const repos = state => state.repos;
\ No newline at end of file
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import actions from './actions'; import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -31,7 +32,8 @@ export default new Vuex.Store({ ...@@ -31,7 +32,8 @@ export default new Vuex.Store({
* } * }
*/ */
repos: [], repos: [],
},
actions, actions,
getters,
mutations, mutations,
},
}); });
export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT';
export const FETCH_REPOS_LIST = 'FETCH_REPOS_LIST'; export const FETCH_REPOS_LIST = 'FETCH_REPOS_LIST';
export const DELETE_REPO = 'DELETE_REPO'; export const DELETE_REPO = 'DELETE_REPO';
export const SET_REPOS_LIST = 'SET_REPOS_LIST'; export const SET_REPOS_LIST = 'SET_REPOS_LIST';
export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING'; export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING';
export const FETCH_IMAGES_LIST = 'FETCH_IMAGES_LIST'; export const FETCH_IMAGES_LIST = 'FETCH_IMAGES_LIST';
export const SET_IMAGES_LIST = 'SET_IMAGES_LIST'; export const SET_REGISTRY_LIST = 'SET_REGISTRY_LIST';
export const DELETE_IMAGE = 'DELETE_IMAGE'; export const DELETE_IMAGE = 'DELETE_IMAGE';
export const TOGGLE_IMAGE_LOADING = 'TOGGLE_MAIN_LOADING'; export const TOGGLE_REGISTRY_LIST_LOADING = 'TOGGLE_REGISTRY_LIST_LOADING';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
[types.SET_MAIN_ENDPOINT](state, endpoint) {
Object.assign(state, { endpoint });
},
[types.SET_REPOS_LIST](state, list) { [types.SET_REPOS_LIST](state, list) {
Object.assign(state, { Object.assign(state, {
repos: list.map(el => ({ repos: list.map(el => ({
name: el.name,
isLoading: false,
canDelete: !!el.destroy_path, canDelete: !!el.destroy_path,
destroyPath: el.destroy_path, destroyPath: el.destroy_path,
isLoading: false,
list: [], list: [],
location: el.location,
name: el.name,
tagsPath: el.tags_path,
id: el.id,
})), })),
}); });
}, },
...@@ -17,8 +25,29 @@ export default { ...@@ -17,8 +25,29 @@ export default {
Object.assign(state, { isLoading: !state.isLoading }); Object.assign(state, { isLoading: !state.isLoading });
}, },
[types.SET_IMAGES_LIST](state, image, list) { [types.SET_REGISTRY_LIST](state, repo, list) {
const listToUpdate = state.repos.find(el => el.name === image.name); // mock
list = [
{
name: 'centos6',
short_revision: '0b6091a66',
revision: '0b6091a665af68bbbbb36a3e088ec3cd6f35389deebf6d4617042d56722d76fb',
size: 706,
layers: 19,
created_at: 1505828744434,
},
{
name: 'centos7',
short_revision: 'b118ab5b0',
revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
size: 679,
layers: 19,
created_at: 1505828744434,
},
];
const listToUpdate = state.repos.find(el => el.id === repo.id);
listToUpdate.list = list.map(element => ({ listToUpdate.list = list.map(element => ({
tag: element.name, tag: element.name,
revision: element.revision, revision: element.revision,
...@@ -31,8 +60,8 @@ export default { ...@@ -31,8 +60,8 @@ export default {
})); }));
}, },
[types.TOGGLE_IMAGE_LOADING](state, image) { [types.TOGGLE_REGISTRY_LIST_LOADING](state, list) {
const listToUpdate = state.repos.find(el => el.name === image.name); const listToUpdate = state.repos.find(el => el.id === list.id);
listToUpdate.isLoading = !listToUpdate.isLoading; listToUpdate.isLoading = !listToUpdate.isLoading;
}, },
}; };
...@@ -14,11 +14,11 @@ ...@@ -14,11 +14,11 @@
}, },
}, },
mounted() { mounted() {
return new Clipboard(this.$refs.btn, { // return new Clipboard(this.$refs.btn, {
text: () => { // text: () => {
return this.text; // return this.text;
}, // },
}); // });
} }
}; };
</script> </script>
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
.container-image-head { .container-image-head {
padding: 0 16px; padding: 0 16px;
line-height: 4em; line-height: 4em;
&:hover {
text-decoration: underline;
}
} }
.table.tags { .table.tags {
......
...@@ -6,6 +6,37 @@ module Projects ...@@ -6,6 +6,37 @@ module Projects
def index def index
@images = project.container_repositories @images = project.container_repositories
respond_to do |format|
format.html
format.json do
# render json: @images
render json: [
{
name: 'gitlab-org/omnibus-gitlab/foo',
tags_path: 'foo',
destroy_path: 'bar',
location: 'foo',
id: '134',
destroy_path: 'bar'
},
{
name: 'gitlab-org/omnibus-gitlab',
tags_path: 'foo',
destroy_path: 'bar',
location: 'foo',
id: '123',
},
{
name: 'gitlab-org/omnibus-gitlab/bar',
tags_path: 'foo',
destroy_path: 'bar',
location: 'foo',
id: '973',
}
]
end
end
end end
def destroy def destroy
......
...@@ -52,9 +52,15 @@ ...@@ -52,9 +52,15 @@
#{escape_once(@project.container_registry_url)}/optional-image-name:tag #{escape_once(@project.container_registry_url)}/optional-image-name:tag
#{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag #{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag
- if @images.blank? #js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json)}}
%p.settings-message.text-center.append-bottom-default
No container images stored for this project. Add one by following the = page_specific_javascript_bundle_tag('common_vue')
instructions above. = page_specific_javascript_bundle_tag('registry_list')
- else
= render partial: 'image', collection: @images
-# - if @images.blank?
-# %p.settings-message.text-center.append-bottom-default
-# No container images stored for this project. Add one by following the
-# instructions above.
-# - else
-# = render partial: 'image', collection: @images
...@@ -67,6 +67,7 @@ var config = { ...@@ -67,6 +67,7 @@ var config = {
prometheus_metrics: './prometheus_metrics', prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches', protected_branches: './protected_branches',
protected_tags: './protected_tags', protected_tags: './protected_tags',
registry_list: './registry/index.js',
repo: './repo/index.js', repo: './repo/index.js',
sidebar: './sidebar/sidebar_bundle.js', sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js', schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
...@@ -199,6 +200,7 @@ var config = { ...@@ -199,6 +200,7 @@ var config = {
'pdf_viewer', 'pdf_viewer',
'pipelines', 'pipelines',
'pipelines_details', 'pipelines_details',
'registry_list',
'repo', 'repo',
'schedule_form', 'schedule_form',
'schedules_index', 'schedules_index',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment