From 63b3abfc0b390a1f1b13a94dedd199e1d87eda05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rune=20Moskvil=20Lyng=C3=A5s?= Date: Fri, 3 Jun 2022 15:33:20 +0200 Subject: [PATCH] First implementation for apikeys view --- src/hooks/useKeysAPI/index.js | 109 +++++++++++++++++++++++++++++++++ src/pages/APIKeys/index.js | 112 +++++++++++++++++++++++++++++----- src/pages/APIKeys/styles.scss | 11 ++++ 3 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 src/hooks/useKeysAPI/index.js diff --git a/src/hooks/useKeysAPI/index.js b/src/hooks/useKeysAPI/index.js new file mode 100644 index 0000000..477ce57 --- /dev/null +++ b/src/hooks/useKeysAPI/index.js @@ -0,0 +1,109 @@ +import { useEffect, useMemo, useState } from 'react' +import axios from 'axios' +import { orderBy } from 'lodash' + +import { API } from '../../config' + +export function useKeysAPI (defaultItemsOptions = {}, top = 1000000) { + const [_keys, setKeys] = useState([]) + const [itemsOptions, setItemsOptions] = useState(defaultItemsOptions) + const [loading, setLoading] = useState(false) + const [updating, setUpdating] = useState(false) + + useEffect(() => { + const getApiKeys = async () => { + setLoading(true) + try { + const options = { + headers: { + 'X-API-KEY': API.TOKEN + } + } + + const url = `${API.URL}/apikeys?$top=${top}` + const { data } = await axios.get(url, options) + setKeys(data.data) + } catch (error) { + console.log('Failed to get api keys') + console.dir(error) + setKeys([]) + } + setLoading(false) + } + + getApiKeys() + }, [top]) + + const options = useMemo(() => { + return { + filter: [], + orderBy: ['createdAt'], + order: 'desc', + ...itemsOptions + } + }, [itemsOptions]) + + const keys = useMemo(() => { + if (options.filter.length === 0) return orderBy(_keys, options.orderBy, options.order) + + const filtered = _keys.filter(item => options.filter.includes(item.status)) // TODO: Needs rework as status doesn't exist in keys + return orderBy(filtered, options.orderBy, options.order) + }, [options, _keys]) + + const updateKeysItem = async (id, updateObject) => { + const options = { + headers: { + 'X-API-KEY': API.TOKEN + } + } + + try { + setUpdating(true) + const { data } = await axios.put(`${API.URL}/apikeys/${id}`, updateObject, options) + const tempKeys = _keys.map(q => { + if (q._id === data._id) { + q = data + } + return q + }) + setKeys(tempKeys) + setUpdating(false) + return data + } catch (error) { + setUpdating(false) + throw error + } + } + + // remove keys item + const removeKeysItem = async (id) => { + const options = { + headers: { + 'X-API-KEY': API.TOKEN + } + } + + try { + setUpdating(true) + await axios.delete(`${API.URL}/apikeys/${id}`, options) + const tempKeys = _keys.filter(q => q._id !== id) + setKeys(tempKeys) + setUpdating(false) + return true + } catch (error) { + setUpdating(false) + throw error + } + } + + return { + allKeys: _keys, + itemsOptions: options, + loading, + keys, + removeKeysItem, + setItemsOptions, + updateKeysItem, + updating + } +} diff --git a/src/pages/APIKeys/index.js b/src/pages/APIKeys/index.js index 01cc130..e0499e6 100644 --- a/src/pages/APIKeys/index.js +++ b/src/pages/APIKeys/index.js @@ -1,23 +1,105 @@ +import { relativeDateFormat } from '@vtfk/utilities' +import { IconButton, Table } from '@vtfk/components' +import React, { useState } from 'react' +import { isEqual } from 'lodash' + +import ConfirmationDialog from '../../components/ConfirmationDialog' + +import { useKeysAPI } from '../../hooks/useKeysAPI' + import './styles.scss' export function APIKeys () { + const { itemsOptions, keys, loading, removeKeysItem, setItemsOptions, updating } = useKeysAPI() + const [itemIndex, setItemIndex] = useState(-1) + + const headers = [ + { + label: 'Name', + value: 'name', + onClick: () => handleSortClick(['name']) + }, + { + label: 'Created', + value: 'createdAt', + itemTooltip: (value, item, header, index) =>
{relativeDateFormat({ toDate: new Date(item.createdAt || item.createdTimestamp), locale: 'no' })}
, + onClick: () => handleSortClick(['createdAt']) + }, + { + label: 'Modified', + value: 'updatedAt', + itemTooltip: (value, item, header, index) =>
{relativeDateFormat({ toDate: new Date(item.updatedAt || item.modifiedTimestamp), locale: 'no' })}
, + onClick: () => handleSortClick(['updatedAt']) + }, + { + label: 'Actions', + itemRender: (value, item, header, index) => { + return ( +
+ setItemIndex(index)} + title='Delete' + /> +
+ ) + } + } + ] + + function handleSortClick (properties) { + setItemsOptions({ + ...itemsOptions, + orderBy: properties, + order: isEqual(itemsOptions.orderBy, properties) ? (itemsOptions.order === 'asc' ? 'desc' : 'asc') : 'desc' + }) + } + + const handleConfirmationOkClick = async id => { + try { + await removeKeysItem(id) + console.log('Successfully removed key', id) + setItemIndex(-1) + } catch (error) { + const removeFailed = error.response?.data?.message || error.message || error + console.log('Failed to remove key:', removeFailed) + // TODO: Add toast for error message + } + } + return (
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
- APIKeys
+
+ console.log('Add new key')} + title='Add key' + + /> + + + + { + itemIndex > -1 && + {`Delete api key ${keys[itemIndex].name} ?`}} + okBtnText='Yes' + cancelBtnText='No' + okBtnDisabled={updating} + onClickCancel={() => setItemIndex(-1)} + onClickOk={() => handleConfirmationOkClick(keys[itemIndex]._id)} + onDismiss={() => setItemIndex(-1)} + height='30%' + width='30%' + /> + } ) } diff --git a/src/pages/APIKeys/styles.scss b/src/pages/APIKeys/styles.scss index e69de29..426a52e 100644 --- a/src/pages/APIKeys/styles.scss +++ b/src/pages/APIKeys/styles.scss @@ -0,0 +1,11 @@ +.apikeys { + .add-key-button { + margin-bottom: 1rem; + + } + + .item-actions { + display: flex; + gap: 0.5rem; + } +}