From feedd72fd2cb44b16c4b4b4c778008cd4c89248a Mon Sep 17 00:00:00 2001 From: Gabriel De Almeida Date: Wed, 7 Jul 2021 13:20:12 -0400 Subject: [PATCH 01/12] Initial setup with router. --- ui/src/components/Access.vue | 9 +++++++++ ui/src/router/index.js | 6 ++++++ 2 files changed, 15 insertions(+) create mode 100644 ui/src/components/Access.vue diff --git a/ui/src/components/Access.vue b/ui/src/components/Access.vue new file mode 100644 index 0000000..fd65961 --- /dev/null +++ b/ui/src/components/Access.vue @@ -0,0 +1,9 @@ + + + diff --git a/ui/src/router/index.js b/ui/src/router/index.js index 1ac2045..c8b5ca5 100644 --- a/ui/src/router/index.js +++ b/ui/src/router/index.js @@ -11,6 +11,7 @@ import Unlock from "../components/Unlock"; import Browse from "../components/Browse"; import Backup from "../components/Backup"; import Usage from "../components/Usage"; +import Access from "../components/Access"; const routes = [ { @@ -42,6 +43,11 @@ const routes = [ path: "usage", component: Usage }, + { + name: "access", + path: "access" + Component: Access + }, { name: "buckets", path: "buckets", From 34f706b5f85c6c2b396d39285db4209a817997fb Mon Sep 17 00:00:00 2001 From: Gabriel De Almeida Date: Thu, 8 Jul 2021 10:15:42 -0400 Subject: [PATCH 02/12] Simple component now rendering properly with the router --- ui/src/components/Access.vue | 2 +- ui/src/router/index.js | 4 ++-- ui/src/views/Dashboard.vue | 9 ++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ui/src/components/Access.vue b/ui/src/components/Access.vue index fd65961..70c2212 100644 --- a/ui/src/components/Access.vue +++ b/ui/src/components/Access.vue @@ -1,5 +1,5 @@ diff --git a/ui/src/components/AccessTableEntry.vue b/ui/src/components/AccessTableEntry.vue new file mode 100644 index 0000000..ce186a6 --- /dev/null +++ b/ui/src/components/AccessTableEntry.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/ui/src/components/AccessTableHeader.vue b/ui/src/components/AccessTableHeader.vue new file mode 100644 index 0000000..0c9b54e --- /dev/null +++ b/ui/src/components/AccessTableHeader.vue @@ -0,0 +1,74 @@ + + + + + From 096bf5faa0aea8668f1289a6ba4b6fa1bdd84c52 Mon Sep 17 00:00:00 2001 From: Gabriel De Almeida Date: Tue, 13 Jul 2021 15:53:56 -0400 Subject: [PATCH 04/12] The basic structure and most functionality of the access module has been created. Most of the sorting functionality is in place. --- ui/package.json | 3 +- ui/src/components/Access.vue | 54 +-------- ui/src/components/AccessTableEntry.vue | 12 +- ui/src/components/AccessTableHeader.vue | 82 ++++++++++--- ui/src/store/access.js | 152 ++++++++++++++++++++++++ ui/src/store/index.js | 2 + 6 files changed, 231 insertions(+), 74 deletions(-) create mode 100644 ui/src/store/access.js diff --git a/ui/package.json b/ui/package.json index 6d9a5f7..0f36514 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,7 +15,8 @@ "vue": "^3.1.4", "vue-router": "^4.0.10", "vuex": "^4.0.2", - "human-readable-numbers": "0.9.5" + "human-readable-numbers": "0.9.5", + "ramda": "^0.27.1" }, "devDependencies": { "@vue/cli-plugin-router": "~4.5.13", diff --git a/ui/src/components/Access.vue b/ui/src/components/Access.vue index 884b65b..b576348 100644 --- a/ui/src/components/Access.vue +++ b/ui/src/components/Access.vue @@ -35,62 +35,12 @@ export default { }), computed: { accessKeys() { - // retrieve access keys - - const demoAccessObjects = [ - { - name: "FoodApp", - permissions: "Read, Write", - duration: "Forever", - buckets: "All", - dateCreated: "07/06/2021 12:27PM EST" - }, - { - name: "Iris", - permissions: "Read, Write, List, Delete", - duration: "06/21/2021 - 08/01/2021", - buckets: "5", - dateCreated: "06/21/2021 10:04AM EST" - }, - { - name: "NFT Kitties", - permissions: "Read, Write, List", - duration: "05/14/2021 - 09/22/2021", - buckets: "2", - dateCreated: "05/14/2021 3:11PM EST" - }, - { - name: "Todo iOS App", - permissions: "List, Delete", - duration: "04/29/2021 - 08/05/2021", - buckets: "8", - dateCreated: "04/29/2021 8:02AM EST" - }, - { - name: "Watermelon Picker", - permissions: "Write", - duration: "Forever", - buckets: "All", - dateCreated: "03/19/2021 7:12PM EST" - }, - { - name: "Accounting", - permissions: "Read, Write, Delete", - duration: "Forever", - buckets: "13", - dateCreated: "01/30/2021 11:20PM EST" - } - ]; - - return demoAccessObjects; - }, - sortAccessKeys() { - + return this.$store.getters["access/sortedAccessKeys"]; } }, async created() { // fetch access keys - // await this.$store.dispatch(""); + await this.$store.dispatch("access/list"); this.loadingAccessSpinner = false; }, components: { diff --git a/ui/src/components/AccessTableEntry.vue b/ui/src/components/AccessTableEntry.vue index ce186a6..3d30282 100644 --- a/ui/src/components/AccessTableEntry.vue +++ b/ui/src/components/AccessTableEntry.vue @@ -5,6 +5,7 @@ } .form-check-input { margin-top: 0px; + cursor: pointer; } label { font-weight: bold; @@ -160,13 +161,14 @@ export default { name: "AccessTableEntry", props: ["access"], data: () => ({ - dropdownOpen: false, deleteConfirmation: false }), computed: { accessBeingDeleted() { - // check if access key is being deleted in the store - return false; + return this.$store.state.access.accessKeysBeingDeleted.find((access) => access.name === this.access.key); + }, + dropdownOpen() { + return this.$store.state.access.openedDropdown === this.access.name; } }, methods: { @@ -174,9 +176,9 @@ export default { event.stopPropagation(); if (this.dropdownOpen) { - this.dropdownOpen = false; + this.$store.dispatch("access/closeDropdown"); } else { - this.dropdownOpen = true; + this.$store.dispatch("access/openDropdown", this.access.name); } }, confirmDeletion(event) { diff --git a/ui/src/components/AccessTableHeader.vue b/ui/src/components/AccessTableHeader.vue index 0c9b54e..90010cb 100644 --- a/ui/src/components/AccessTableHeader.vue +++ b/ui/src/components/AccessTableHeader.vue @@ -18,47 +18,50 @@ thead th { .name { font-weight: bold; } +.arrow { + cursor: pointer; +} diff --git a/ui/src/store/access.js b/ui/src/store/access.js new file mode 100644 index 0000000..d5fceff --- /dev/null +++ b/ui/src/store/access.js @@ -0,0 +1,152 @@ +import * as R from "ramda"; + +export default { + namespaced: true, + state: () => ({ + accessKeys: [], + headingSorted: "name", + orderBy: "asc", + openedDropdown: null, + accessCreationModal: null, + accessKeysBeingDeleted: [] + }), + getters: { + sortedAccessKeys(state) { + // temporary for testing purposes + return state.accessKeys; + + const duration = (a, b) => { + if (a === "forever") { + return 1; + } else if (b === "forever") { + return -1; + } + + // find difference between dates and return accordingly + }; + + // key-specific sort cases + const fns = { + name: (a, b) => a.name.localeCompare(b.name), + duration: duration, + dateCreated: (a, b) => new Date(a.dateCreated) - new Date(b.dateCreated) + }; + + // sort by appropriate function + const sortedKeys = R.sort(fns[state.headingSorted], state.accessKeys); + + // reverse if descending order + const orderedAccessKeys = + state.orderBy === "asc" ? sortedKeys : R.reverse(sortedKeys); + + return orderedAccessKeys; + } + }, + mutations: { + setAccessKey(state, key) { + state.accessKeys = [...state.accessKeys, key]; + }, + updateAccessKeys(state, keys) { + state.accessKeys = keys; + }, + setDropdown(state, id) { + state.openedDropdown = id; + }, + setHeadingSorted(state, heading) { + state.headingSorted = heading; + }, + setOrderBy(state, order) { + state.orderBy = order; + }, + setAccessCreationModal(state, value) { + state.accessCreationModal = value; + }, + setAccessKeysBeingDeleted(state, keys) { + state.accessKeysBeingDeleted = [...state.accessKeysBeingDeleted, ...keys]; + }, + removeAccessKeyBeingDeleted(state, key) { + state.accessKeysBeingDeleted = state.accessKeysBeingDeleted.filter((accessKey) => accessKey !== key); + } + }, + actions: { + async list({ commit }) { + // fetch access keys + + const demoAccessObjects = [ + { + name: "FoodApp", + permissions: "Read, Write", + duration: "Forever", + buckets: "All", + dateCreated: "07/06/2021 12:27PM EST" + }, + { + name: "Iris", + permissions: "Read, Write, List, Delete", + duration: "06/21/2021 - 08/01/2021", + buckets: "5", + dateCreated: "06/21/2021 10:04AM EST" + }, + { + name: "NFT Kitties", + permissions: "Read, Write, List", + duration: "05/14/2021 - 09/22/2021", + buckets: "2", + dateCreated: "05/14/2021 3:11PM EST" + }, + { + name: "Todo iOS App", + permissions: "List, Delete", + duration: "04/29/2021 - 08/05/2021", + buckets: "8", + dateCreated: "04/29/2021 8:02AM EST" + }, + { + name: "Watermelon Picker", + permissions: "Write", + duration: "Forever", + buckets: "All", + dateCreated: "03/19/2021 7:12PM EST" + }, + { + name: "Accounting", + permissions: "Read, Write, Delete", + duration: "Forever", + buckets: "13", + dateCreated: "01/30/2021 11:20PM EST" + } + ]; + + commit("updateAccessKeys", demoAccessObjects); + }, + async createAccessKey({ commit }, { key }) { + // create access key + // commit("setAccessKey", key); + }, + async deleteAccessKey({ commit }, key) { + // find key by name + // commit("setAccessKeysBeingDeleted", [key]); + // delete access key + // commit("removeAccessKeyBeingDeleted", key); + }, + async deleteSelectedAccessKeys({ dispatch }, keys) { + await Promise.all(keys.map((key) => dispatch("deleteAccessKey"))); + }, + openDropdown({ commit }, id) { + commit("setDropdown", id); + }, + closeDropdown({ commit }) { + commit("setDropdown", null); + }, + sort({ commit, state }, heading) { + const flip = (orderBy) => (orderBy === "asc" ? "desc" : "asc"); + + const order = state.headingSorted === heading ? + flip(state.orderBy) : + "asc"; + + commit("setOrderBy", order); + commit("setHeadingSorted", heading); + } + } +} diff --git a/ui/src/store/index.js b/ui/src/store/index.js index a7efa8c..d6ddf8c 100644 --- a/ui/src/store/index.js +++ b/ui/src/store/index.js @@ -3,6 +3,7 @@ import { createStore } from "vuex"; import account from "./account"; import gateway from "./gateway"; import buckets from "./buckets"; +import access from "./access"; import files from "../../browser/src/store/files"; export default createStore({ @@ -13,6 +14,7 @@ export default createStore({ account, gateway, buckets, + access, files } }); From d720f0202a4daac3a19cffafbbb07e412f353a5b Mon Sep 17 00:00:00 2001 From: Gabriel De Almeida Date: Wed, 14 Jul 2021 15:19:13 -0400 Subject: [PATCH 05/12] Sorting is working properly. Successful requests are being made to the Satellite to retrieve access keys. Access Keys has been renamed to Keys. We're currently only supporting the display of Name and Date Created. --- ui/package.json | 3 +- ui/src/App.vue | 1 + ui/src/components/Access.vue | 12 ++- ui/src/components/AccessTableEntry.vue | 23 +++-- ui/src/components/AccessTableHeader.vue | 100 +++++++++--------- ui/src/lib/satellite.js | 50 +++++++++ ui/src/store/access.js | 128 +++++++++--------------- ui/src/views/Dashboard.vue | 2 +- 8 files changed, 176 insertions(+), 143 deletions(-) diff --git a/ui/package.json b/ui/package.json index 0f36514..6d9a5f7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,8 +15,7 @@ "vue": "^3.1.4", "vue-router": "^4.0.10", "vuex": "^4.0.2", - "human-readable-numbers": "0.9.5", - "ramda": "^0.27.1" + "human-readable-numbers": "0.9.5" }, "devDependencies": { "@vue/cli-plugin-router": "~4.5.13", diff --git a/ui/src/App.vue b/ui/src/App.vue index 0122cd4..5d99e9c 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -57,6 +57,7 @@ export default { closeAllInteractions() { this.$store.dispatch("files/closeAllInteractions"); + this.$store.dispatch("access/closeAllInteractions"); // close dropdrowns in access page and buckets if it has any } diff --git a/ui/src/components/Access.vue b/ui/src/components/Access.vue index b576348..5c85dab 100644 --- a/ui/src/components/Access.vue +++ b/ui/src/components/Access.vue @@ -5,14 +5,17 @@ tbody { @@ -159,14 +237,33 @@ export default { nameDescFill: descFill("name"), nameAscFill: ascFill("name"), dateCreatedDescFill: descFill("dateCreated"), - dateCreatedAscFill: ascFill("dateCreated") + dateCreatedAscFill: ascFill("dateCreated"), // durationDescFill: descFill("duration"), - // durationAscFill: ascFill("duration") + // durationAscFill: ascFill("duration"), + + keysToDelete() { + return this.$store.state.access.selectedAccessKeys.length > 0; + }, + displayDropdown() { + return this.$store.state.access.accessTableDropdown; + } }, methods: { sortByName: sortBy("name"), - sortByDateCreated: sortBy("dateCreated") - // sortByDuration: sortBy("duration") + sortByDateCreated: sortBy("dateCreated"), + // sortByDuration: sortBy("duration"), + + deleteSelectedDropdown(event) { + event.stopPropagation(); + this.$store.dispatch("access/openAccessTableDropdown"); + }, + confirmDeleteSelection() { + this.$store.dispatch("access/deleteSelectedAccessKeys"); + this.$store.dispatch("access/closeAccessTableDropdown"); + }, + cancelDeleteSelection() { + this.$store.dispatch("access/closeAccessTableDropdown"); + } } }; diff --git a/ui/src/lib/satellite.js b/ui/src/lib/satellite.js index 51a4064..1b05878 100644 --- a/ui/src/lib/satellite.js +++ b/ui/src/lib/satellite.js @@ -210,3 +210,35 @@ export async function getAccessKeys({ return { apiKeys, pageCount }; } + +export async function deleteAccessKeys({ + token, + id +}) { + const response = await fetch("/api/v0/graphql", { + method: "POST", + headers: { + "Content-Type": "application/json", + Cookie: `_tokenKey=${token}` + }, + body: JSON.stringify({ + query: `mutation ($id: [String!]!) { + deleteAPIKeys(id: $id) { + id + __typename + } + }`, + variables: { + id + } + }) + }); + + const { + data: { + deleteAPIKeys + } + } = await response.json(); + + return deleteAPIKeys; +} diff --git a/ui/src/store/access.js b/ui/src/store/access.js index 70a9789..1370a78 100644 --- a/ui/src/store/access.js +++ b/ui/src/store/access.js @@ -1,4 +1,4 @@ -import { getAccessKeys } from "../lib/satellite"; +import { getAccessKeys, deleteAccessKeys } from "../lib/satellite"; export default { namespaced: true, @@ -14,6 +14,8 @@ export default { openedDropdown: null, accessCreationModal: null, accessKeysBeingDeleted: [], + accessTableDropdown: null, + selectedAccessKeys: [], satellite: { limit: 10, page: 1, @@ -33,6 +35,9 @@ export default { setDropdown(state, id) { state.openedDropdown = id; }, + setAccessTableDropdown(state, value) { + state.accessTableDropdown = value; + }, setHeadingSorted(state, heading) { state.headingSorted = heading; }, @@ -48,11 +53,21 @@ export default { ...keys ]; }, - removeAccessKeyBeingDeleted(state, key) { - state.accessKeysBeingDeleted = state.accessKeysBeingDeleted.filter( - (accessKey) => accessKey !== key - ); - }, + removeAllAccessKeyBeingDeleted(state) { + state.accessKeysBeingDeleted = []; + }, + setSelectedAccessKey(state, key) { + state.selectedAccessKeys = [ + ...state.selectedAccessKeys, + key + ]; + }, + removeSelectedAccessKey(state, key) { + state.selectedAccessKeys = state.selectedAccessKeys.filter((accessKey) => accessKey !== key); + }, + removeAllSelectedAccessKeys(state) { + state.selectedAccessKeys = []; + }, setPageCount(state, count) { state.pageCount = count; }, @@ -94,14 +109,27 @@ export default { // create access key // commit("setAccessKey", key); }, - async deleteAccessKey({ commit }, key) { - // find key by name - // commit("setAccessKeysBeingDeleted", [key]); - // delete access key - // commit("removeAccessKeyBeingDeleted", key); - }, - async deleteSelectedAccessKeys({ dispatch }, keys) { - await Promise.all(keys.map((key) => dispatch("deleteAccessKey"))); + async deleteAccessKeys({ commit, rootState, dispatch }, keyIDs) { + const { token } = rootState.account; + + if (!Array.isArray(keyIDs)) { + keyIDs = [keyIDs]; + } + + commit("setAccessKeysBeingDeleted", keyIDs); + await deleteAccessKeys({ token, id: keyIDs }); + commit("removeAllAccessKeyBeingDeleted"); + dispatch("list"); + }, + addSelectedAccessKey({ commit }, key) { + commit("setSelectedAccessKey", key); + }, + deleteSelectedAccessKey({ commit }, key) { + commit("removeSelectedAccessKey", key); + }, + async deleteSelectedAccessKeys({ dispatch, commit, state }) { + dispatch("deleteAccessKeys", [...state.selectedAccessKeys]); + commit("removeAllSelectedAccessKeys"); }, openDropdown({ commit }, id) { commit("setDropdown", id); @@ -109,6 +137,12 @@ export default { closeDropdown({ commit }) { commit("setDropdown", null); }, + openAccessTableDropdown({ commit }) { + commit("setAccessTableDropdown", true); + }, + closeAccessTableDropdown({ commit }) { + commit("setAccessTableDropdown", null); + }, sort({ commit, state, dispatch }, heading) { const flip = (orderBy) => (orderBy === "asc" ? "desc" : "asc"); @@ -124,6 +158,10 @@ export default { if (state.openedDropdown) { dispatch("closeDropdown"); } + + if (state.accessTableDropdown) { + dispatch("closeAccessTableDropdown"); + } } } }; From bfd8f3ba59c6f648be20c33dc927f8a77e01be2b Mon Sep 17 00:00:00 2001 From: Gabriel De Almeida Date: Fri, 16 Jul 2021 14:18:21 -0400 Subject: [PATCH 08/12] prettierize --- ui/src/components/AccessTableEntry.vue | 32 ++++++---- ui/src/components/AccessTableHeader.vue | 18 +++--- ui/src/lib/satellite.js | 9 +-- ui/src/store/access.js | 83 ++++++++++++------------- 4 files changed, 71 insertions(+), 71 deletions(-) diff --git a/ui/src/components/AccessTableEntry.vue b/ui/src/components/AccessTableEntry.vue index 3f3e3e0..365bf6b 100644 --- a/ui/src/components/AccessTableEntry.vue +++ b/ui/src/components/AccessTableEntry.vue @@ -24,7 +24,7 @@ td { class="form-check-input" type="checkbox" id="{{access.id}}" - v-model="selectionCheckbox" + v-model="selectionCheckbox" />