[ci skip] First iteration: port haml into vue

This commit is contained in:
Filipa Lacerda 2017-09-19 16:38:55 +01:00
parent 23024a70db
commit 51c9f8b6d6
14 changed files with 324 additions and 77 deletions

View File

@ -0,0 +1,90 @@
<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>

View File

@ -1,24 +1,22 @@
<script>
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 {
name: 'collapsibeContainerRegisty',
props: {
title: {
type: String,
required: true,
},
clipboardContent: {
type: String,
required: true,
},
repoData: {
repo: {
type: Object,
required: true,
},
},
components: {
clipboardButton,
loadingIcon,
},
directives: {
tooltip,
},
data() {
return {
@ -26,37 +24,73 @@
};
},
methods: {
itemSize(item) {
layers(item) {
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>
<template>
<div class="container-image">
<div class="container-image-head">
<i
class="fa"
:class="{
'chevron-left': !isOpen,
'chevron-up': isOpen,
}"
aria-hidden="true">
</i>
{{title}}
<clipboard-button
:text=""
:title=""
/>
</div>
<div
class="container-image-tags"
:class="{ hide: !isOpen }">
class="container-image-head">
<a
role="button"
@click="toggleRepo">
<i
class="fa"
:class="{
'fa-chevron-right': !isOpen,
'fa-chevron-up': isOpen,
}"
aria-hidden="true">
</i>
{{repo.name}}
</a>
<table class="table tags" v-if="true">
<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>
</div>
<loading-icon
v-if="repo.isLoading"
/>
<div
v-else-if="!repo.isLoading && isOpen"
class="container-image-tags">
<table class="table tags" v-if="repo.list.length">
<thead>
<tr>
<th>{{__("Tag")}}</th>
@ -71,23 +105,28 @@
v-for="(item, i) in repo.list"
:key="i">
<td>
{{item.name}}
{{item.tag}}
<clipboard-button
:title="item.location"
:text="item.location"
:title="item.tag"
:text="item.tag"
/>
</td>
<td>
<span
v-tooltip
:title="item.revision"
data-placement="bottom"
>
{{item.shortRevision}}
</span>
</td>
<td>
<template v-if="item.size">
{{itemSize(item)}}
{{item.size}}
&middot;
{{layers(item)}}
</template>
<div v-else class="light">
\-
@ -103,18 +142,20 @@
</div>
</td>
<td>
<button
type="button"
class="btn btn-remove"
title="Remove tag"
v-tooltip
@click="deleteTag(item)">
<i
class="fa fa-trash cred"
aria-hidden="true">
</i>
</button>
<td class="content">
<div class="controls hidden-xs pull-right">
<button
type="button"
class="btn btn-remove"
title="Remove tag"
v-tooltip
@click="handleDeleteRegistry(item)">
<i
class="fa fa-trash"
aria-hidden="true">
</i>
</button>
</div>
</td>
</tr>
</tbody>
@ -123,9 +164,7 @@
v-else
class="nothing-here-block">
{{__("No tags in Container Registry for this container image.")}}
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,13 @@
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.',
};

View File

@ -0,0 +1,25 @@
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,
},
});
},
}));

View File

@ -16,13 +16,13 @@ export const fetchRepos = ({ commit, state }) => {
};
export const fetchList = ({ commit }, list) => {
commit(types.TOGGLE_IMAGE_LOADING, list);
commit(types.TOGGLE_REGISTRY_LIST_LOADING, list);
return Vue.http.get(list.path)
.then(res => res.json())
.then((response) => {
commit(types.TOGGLE_IMAGE_LOADING, list);
commit(types.SET_IMAGES_LIST, list, response);
commit(types.TOGGLE_REGISTRY_LIST_LOADING, list);
commit(types.SET_REGISTRY_LIST, list, response);
});
};
@ -32,8 +32,11 @@ export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.path)
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(() => {
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);

View File

@ -0,0 +1,2 @@
export const isLoading = state => state.isLoading;
export const repos = state => state.repos;

View File

@ -1,6 +1,7 @@
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
@ -31,7 +32,8 @@ export default new Vuex.Store({
* }
*/
repos: [],
actions,
mutations,
},
actions,
getters,
mutations,
});

View File

@ -1,9 +1,10 @@
export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT';
export const FETCH_REPOS_LIST = 'FETCH_REPOS_LIST';
export const DELETE_REPO = 'DELETE_REPO';
export const SET_REPOS_LIST = 'SET_REPOS_LIST';
export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING';
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 TOGGLE_IMAGE_LOADING = 'TOGGLE_MAIN_LOADING';
export const TOGGLE_REGISTRY_LIST_LOADING = 'TOGGLE_REGISTRY_LIST_LOADING';

View File

@ -1,14 +1,22 @@
import * as types from './mutation_types';
export default {
[types.SET_MAIN_ENDPOINT](state, endpoint) {
Object.assign(state, { endpoint });
},
[types.SET_REPOS_LIST](state, list) {
Object.assign(state, {
repos: list.map(el => ({
name: el.name,
isLoading: false,
canDelete: !!el.destroy_path,
destroyPath: el.destroy_path,
isLoading: false,
list: [],
location: el.location,
name: el.name,
tagsPath: el.tags_path,
id: el.id,
})),
});
},
@ -17,8 +25,29 @@ export default {
Object.assign(state, { isLoading: !state.isLoading });
},
[types.SET_IMAGES_LIST](state, image, list) {
const listToUpdate = state.repos.find(el => el.name === image.name);
[types.SET_REGISTRY_LIST](state, repo, list) {
// 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 => ({
tag: element.name,
revision: element.revision,
@ -31,8 +60,8 @@ export default {
}));
},
[types.TOGGLE_IMAGE_LOADING](state, image) {
const listToUpdate = state.repos.find(el => el.name === image.name);
[types.TOGGLE_REGISTRY_LIST_LOADING](state, list) {
const listToUpdate = state.repos.find(el => el.id === list.id);
listToUpdate.isLoading = !listToUpdate.isLoading;
},
};

View File

@ -14,11 +14,11 @@
},
},
mounted() {
return new Clipboard(this.$refs.btn, {
text: () => {
return this.text;
},
});
// return new Clipboard(this.$refs.btn, {
// text: () => {
// return this.text;
// },
// });
}
};
</script>

View File

@ -9,6 +9,10 @@
.container-image-head {
padding: 0 16px;
line-height: 4em;
&:hover {
text-decoration: underline;
}
}
.table.tags {

View File

@ -6,6 +6,37 @@ module Projects
def index
@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
def destroy

View File

@ -52,9 +52,15 @@
#{escape_once(@project.container_registry_url)}/optional-image-name:tag
#{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag
- 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
#js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json)}}
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('registry_list')
-# - 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

View File

@ -67,6 +67,7 @@ var config = {
prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
registry_list: './registry/index.js',
repo: './repo/index.js',
sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
@ -199,6 +200,7 @@ var config = {
'pdf_viewer',
'pipelines',
'pipelines_details',
'registry_list',
'repo',
'schedule_form',
'schedules_index',