diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
new file mode 100644
index 00000000000..7014562be08
--- /dev/null
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ {{title}}
+
+
+
+
diff --git a/app/assets/javascripts/registry/components/registry_table.vue b/app/assets/javascripts/registry/components/registry_table.vue
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/app/assets/javascripts/registry/index.js b/app/assets/javascripts/registry/index.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js
new file mode 100644
index 00000000000..6c0286e2be6
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/actions.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+import * as types from './mutation_types';
+
+Vue.use(VueResource);
+
+export const fetchRepos = ({ commit, state }) => {
+ commit(types.TOGGLE_MAIN_LOADING);
+
+ return Vue.http.get(state.endpoint)
+ .then(res => res.json())
+ .then((response) => {
+ commit(types.TOGGLE_MAIN_LOADING);
+ commit(types.SET_REPOS_LIST, response);
+ });
+};
+
+export const fetchList = ({ commit }, list) => {
+ commit(types.TOGGLE_IMAGE_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);
+ });
+};
+
+export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.path)
+ .then(res => res.json())
+ .then(() => {
+ commit(types.DELETE_REPO, repo);
+ });
+
+export const deleteImage = ({ commit }, image) => Vue.http.delete(image.path)
+ .then(res => res.json())
+ .then(() => {
+ commit(types.DELETE_IMAGE, image);
+ });
diff --git a/app/assets/javascripts/registry/stores/index.js b/app/assets/javascripts/registry/stores/index.js
new file mode 100644
index 00000000000..6cf9df57f08
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/index.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ state: {
+ isLoading: false,
+ endpoint: '', // initial endpoint to fetch the repos list
+ /**
+ * Each object in `repos` has the following strucure:
+ * {
+ * name: String,
+ * isLoading: Boolean,
+ * tagsPath: String // endpoint to request the list
+ * destroyPath: String // endpoit to delete the repo
+ * list: Array // List of the registry images
+ * }
+ *
+ * Each registry image inside `list` has the following structure:
+ * {
+ * tag: String,
+ * revision: String
+ * shortRevision: String
+ * size: Number
+ * layers: Number
+ * createdAt: String
+ * destroyPath: String // endpoit to delete each image
+ * }
+ */
+ repos: [],
+ actions,
+ mutations,
+ },
+});
diff --git a/app/assets/javascripts/registry/stores/mutation_types.js b/app/assets/javascripts/registry/stores/mutation_types.js
new file mode 100644
index 00000000000..fb4e24e10e3
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/mutation_types.js
@@ -0,0 +1,9 @@
+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 DELETE_IMAGE = 'DELETE_IMAGE';
+export const TOGGLE_IMAGE_LOADING = 'TOGGLE_MAIN_LOADING';
diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js
new file mode 100644
index 00000000000..5fa41fb5255
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/mutations.js
@@ -0,0 +1,38 @@
+import * as types from './mutation_types';
+
+export default {
+ [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,
+ list: [],
+ })),
+ });
+ },
+
+ [types.TOGGLE_MAIN_LOADING](state) {
+ Object.assign(state, { isLoading: !state.isLoading });
+ },
+
+ [types.SET_IMAGES_LIST](state, image, list) {
+ const listToUpdate = state.repos.find(el => el.name === image.name);
+ listToUpdate.list = list.map(element => ({
+ tag: element.name,
+ revision: element.revision,
+ shortRevision: element.short_revision,
+ size: element.size,
+ layers: element.layers,
+ createdAt: element.created_at,
+ destroyPath: element.destroy_path,
+ canDelete: !!element.destroy_path,
+ }));
+ },
+
+ [types.TOGGLE_IMAGE_LOADING](state, image) {
+ const listToUpdate = state.repos.find(el => el.name === image.name);
+ listToUpdate.isLoading = !listToUpdate.isLoading;
+ },
+};
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
new file mode 100644
index 00000000000..ebb3dbd0112
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -0,0 +1,40 @@
+
+import Clipboard from 'vendor/clipboard';
+
+
+
+
+
+