diff --git a/ui/src/App.vue b/ui/src/App.vue index ed66128..5d99e9c 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -57,6 +57,9 @@ export default { closeAllInteractions() { this.$store.dispatch("files/closeAllInteractions"); + this.$store.dispatch("access/closeAllInteractions"); + + // close dropdrowns in access page and buckets if it has any } }, watch: { diff --git a/ui/src/components/Access.vue b/ui/src/components/Access.vue new file mode 100644 index 0000000..accdc0e --- /dev/null +++ b/ui/src/components/Access.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/ui/src/components/AccessTableEntry.vue b/ui/src/components/AccessTableEntry.vue new file mode 100644 index 0000000..ea55aed --- /dev/null +++ b/ui/src/components/AccessTableEntry.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/ui/src/components/AccessTableHeader.vue b/ui/src/components/AccessTableHeader.vue new file mode 100644 index 0000000..ae2d873 --- /dev/null +++ b/ui/src/components/AccessTableHeader.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/ui/src/lib/satellite.js b/ui/src/lib/satellite.js index f5e1eaf..082676e 100644 --- a/ui/src/lib/satellite.js +++ b/ui/src/lib/satellite.js @@ -153,3 +153,87 @@ export async function createApiKey({ token, projectId, name }) { return { key }; } + +export async function getAccessKeys({ + token, + projectId, + limit, + search, + page, + order, + orderDirection +}) { + const response = await fetch("/api/v0/graphql", { + method: "POST", + headers: { + "Content-Type": "application/json", + Cookie: `_tokenKey=${token}` + }, + body: JSON.stringify({ + query: `query ($projectId: String!, $limit: Int!, $search: String!, $page: Int!, $order: Int!, $orderDirection: Int!) { + project(id: $projectId) { + apiKeys(cursor: {limit: $limit, search: $search, page: $page, order: $order, orderDirection: $orderDirection}) { + apiKeys { + id + name + createdAt + } + search + limit + order + pageCount + currentPage + totalCount + __typename + } + __typename + } + }`, + variables: { + projectId, + limit, + search, + page, + order, + orderDirection + } + }) + }); + + const { + data: { + project: { + apiKeys: { apiKeys, pageCount } + } + } + } = await response.json(); + + 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/router/index.js b/ui/src/router/index.js index aab546f..b8d8e25 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"; import UpgradeForm from "../components/UpgradeForm"; const routes = [ @@ -43,6 +44,11 @@ const routes = [ path: "usage", component: Usage }, + { + name: "access", + path: "access", + component: Access + }, { name: "upgrade-form", path: "usage/upgrade-form", diff --git a/ui/src/store/access.js b/ui/src/store/access.js new file mode 100644 index 0000000..233490f --- /dev/null +++ b/ui/src/store/access.js @@ -0,0 +1,166 @@ +import { getAccessKeys, deleteAccessKeys } from "../lib/satellite"; + +export default { + namespaced: true, + state: () => ({ + accessKeys: [], + headingSorted: "name", + orderBy: "asc", + asc: 2, + desc: 1, + pageCount: 1, + nameHeadingOrder: 1, + dateCreatedHeadingOrder: 2, + openedDropdown: null, + accessCreationModal: null, + accessKeysBeingDeleted: [], + accessTableDropdown: null, + selectedAccessKeys: [], + satellite: { + limit: 10, + page: 1, + search: "", + order: 1, + orderDirection: 2 + } + }), + getters: {}, + mutations: { + setAccessKey(state, key) { + state.accessKeys = [...state.accessKeys, key]; + }, + updateAccessKeys(state, keys) { + state.accessKeys = keys; + }, + setDropdown(state, id) { + state.openedDropdown = id; + }, + setAccessTableDropdown(state, value) { + state.accessTableDropdown = value; + }, + 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 + ]; + }, + 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; + }, + updateSatelliteRequestObject(state) { + if (state.headingSorted === "name") { + state.satellite.order = state.nameHeadingOrder; + } else { + state.satellite.order = state.dateCreatedHeadingOrder; + } + + if (state.orderBy === "asc") { + state.satellite.orderDirection = state.asc; + } else { + state.satellite.orderDirection = state.desc; + } + } + }, + actions: { + async list({ commit, state, rootState }) { + const { token, projectId } = rootState.account; + const { limit, search, page, order, orderDirection } = + state.satellite; + + const data = await getAccessKeys({ + token, + projectId, + limit, + search, + page, + order, + orderDirection + }); + const { apiKeys, pageCount } = data; + + commit("updateAccessKeys", apiKeys); + commit("setPageCount", pageCount); + }, + async createAccessKey({ commit }, { key }) { + // create access key + // commit("setAccessKey", key); + }, + 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); + }, + 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"); + + const order = + state.headingSorted === heading ? flip(state.orderBy) : "asc"; + + commit("setOrderBy", order); + commit("setHeadingSorted", heading); + commit("updateSatelliteRequestObject"); + dispatch("list"); + }, + closeAllInteractions({ state, dispatch }) { + if (state.openedDropdown) { + dispatch("closeDropdown"); + } + + if (state.accessTableDropdown) { + dispatch("closeAccessTableDropdown"); + } + } + } +}; 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 } }); diff --git a/ui/src/views/Dashboard.vue b/ui/src/views/Dashboard.vue index 16630ef..fc32e5f 100644 --- a/ui/src/views/Dashboard.vue +++ b/ui/src/views/Dashboard.vue @@ -159,8 +159,11 @@ Backup - @@ -351,6 +354,9 @@ export default { this.$route.path === "/app/usage" || this.$route.path === "/app/usage/upgrade-form" ); + }, + accessPath() { + return this.$route.path === "/app/access"; } }, methods: {