From a751d6cdd7d24c849c271f4e1cd10e01a995927e Mon Sep 17 00:00:00 2001 From: Tom Jeannesson Date: Sun, 15 Oct 2023 00:41:28 +0200 Subject: [PATCH] feat(servers-&&-keys): basic interface --- desktop-app/package.json | 3 + .../api/exchangeAccounts/exchangeAccount.ts | 4 +- desktop-app/renderer/api/key/key.ts | 43 ++++ desktop-app/renderer/api/request.ts | 15 +- desktop-app/renderer/api/spaces/spaces.ts | 27 +++ .../renderer/components/custom/copyButton.tsx | 52 +++++ .../components/custom/headerPopover.tsx | 117 +++++++--- .../components/custom/settingsButton.tsx | 21 ++ .../renderer/components/layout/breadcrumb.tsx | 176 ++++++++------- .../components/layout/contextHeader.tsx | 13 +- desktop-app/renderer/components/ui/badge.tsx | 36 +++ .../renderer/components/ui/checkbox.tsx | 30 +++ .../renderer/components/ui/hover-card.tsx | 29 +++ desktop-app/renderer/components/ui/switch.tsx | 29 +++ desktop-app/renderer/lib/localStorage.ts | 47 ++-- desktop-app/renderer/lib/queryParams.ts | 1 - .../renderer/pages/exchangeAccounts/index.tsx | 7 +- .../renderer/pages/keys/[slug]/index.tsx | 25 +++ .../pages/servers/[slug]/adminFooter.tsx | 156 +++++++++++++ .../renderer/pages/servers/[slug]/index.tsx | 30 ++- .../pages/servers/[slug]/selectedAPIKey.tsx | 208 ++++++++++++++++++ .../pages/servers/[slug]/selectedServer.tsx | 122 ++++++++++ desktop-app/renderer/pages/servers/index.tsx | 24 +- desktop-app/renderer/pages/settings/index.tsx | 9 + .../renderer/pages/spaces/[name]/index.tsx | 21 +- desktop-app/renderer/pages/spaces/index.tsx | 48 ++-- desktop-app/yarn.lock | 45 ++++ 27 files changed, 1167 insertions(+), 171 deletions(-) create mode 100644 desktop-app/renderer/api/key/key.ts create mode 100644 desktop-app/renderer/api/spaces/spaces.ts create mode 100644 desktop-app/renderer/components/custom/copyButton.tsx create mode 100644 desktop-app/renderer/components/custom/settingsButton.tsx create mode 100644 desktop-app/renderer/components/ui/badge.tsx create mode 100644 desktop-app/renderer/components/ui/checkbox.tsx create mode 100644 desktop-app/renderer/components/ui/hover-card.tsx create mode 100644 desktop-app/renderer/components/ui/switch.tsx create mode 100644 desktop-app/renderer/pages/keys/[slug]/index.tsx create mode 100644 desktop-app/renderer/pages/servers/[slug]/adminFooter.tsx create mode 100644 desktop-app/renderer/pages/servers/[slug]/selectedAPIKey.tsx create mode 100644 desktop-app/renderer/pages/servers/[slug]/selectedServer.tsx create mode 100644 desktop-app/renderer/pages/settings/index.tsx diff --git a/desktop-app/package.json b/desktop-app/package.json index b4052f17..f789880f 100644 --- a/desktop-app/package.json +++ b/desktop-app/package.json @@ -15,8 +15,10 @@ "dependencies": { "@hookform/resolvers": "^3.3.0", "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.5", + "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.6", @@ -25,6 +27,7 @@ "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.4", "@radix-ui/react-tooltip": "^1.0.6", diff --git a/desktop-app/renderer/api/exchangeAccounts/exchangeAccount.ts b/desktop-app/renderer/api/exchangeAccounts/exchangeAccount.ts index 2211e339..057f5c46 100644 --- a/desktop-app/renderer/api/exchangeAccounts/exchangeAccount.ts +++ b/desktop-app/renderer/api/exchangeAccounts/exchangeAccount.ts @@ -9,14 +9,14 @@ export interface ExchangeAccount { testing: boolean } -export async function list( +export async function listExchangeAccount( searchParams: ReturnType ): Promise> { const response = await request(searchParams, 'GET', '/api/exchange_account/') return response as AxiosResponse } -export async function get( +export async function getExchangeAccount( searchParams: ReturnType, id: string ): Promise> { diff --git a/desktop-app/renderer/api/key/key.ts b/desktop-app/renderer/api/key/key.ts new file mode 100644 index 00000000..b7e809f7 --- /dev/null +++ b/desktop-app/renderer/api/key/key.ts @@ -0,0 +1,43 @@ +import { request } from 'api/request' +import { AxiosResponse } from 'axios' +import { useSearchParams } from 'next/navigation' + +export interface Key { + name: string + prefix: string + permissions: string[] + is_master_key: boolean + revoked: boolean + description: string +} + +export async function getKey( + searchParams: ReturnType, + id: string +): Promise> { + const response = await request(searchParams, 'GET', `/api/key/${id}/`) + return response as AxiosResponse +} +export async function listKey( + searchParams: ReturnType, + space: string | null = null +): Promise> { + const response = await request( + searchParams, + 'GET', + space ? `/api/key/?space=${space}` : '/api/key/' + ) + return response as AxiosResponse +} + +export async function createKey( + searchParams: ReturnType, + name: string, + description: string +): Promise> { + const response = await request(searchParams, 'POST', '/api/key/', { + name, + description + }) + return response as AxiosResponse<{ key: string }> +} diff --git a/desktop-app/renderer/api/request.ts b/desktop-app/renderer/api/request.ts index 1840a9ad..a52f07cb 100644 --- a/desktop-app/renderer/api/request.ts +++ b/desktop-app/renderer/api/request.ts @@ -1,22 +1,29 @@ import { getServer } from '@/lib/localStorage' -import axios from 'axios' +import axios, { AxiosHeaders } from 'axios' import { useSearchParams } from 'next/navigation' export function request( searchParams: ReturnType, method: string, url: string, - ...args: any[] + data: Object | null = null, + headers: AxiosHeaders | null = null ) { const serverID = searchParams.get('server') if (!serverID) { throw new Error('No server selected') } - const serverUrl = getServer(serverID).url + const server = getServer(serverID) + const serverUrl = server.url + const token = server.token return axios({ method: method, url: url, baseURL: serverUrl, - ...args + headers: { + Authorization: 'Api-Key ' + token, + ...headers + }, + data: data || {} }) } diff --git a/desktop-app/renderer/api/spaces/spaces.ts b/desktop-app/renderer/api/spaces/spaces.ts new file mode 100644 index 00000000..ac4062b6 --- /dev/null +++ b/desktop-app/renderer/api/spaces/spaces.ts @@ -0,0 +1,27 @@ +import { request } from 'api/request' +import { AxiosResponse } from 'axios' +import { useSearchParams } from 'next/navigation' + +export interface NapseSpace { + name: string + uuid: string + value: number + fleet_count: number + exchange_account: string + delta?: number +} + +export async function listSpace( + searchParams: ReturnType +): Promise> { + const response = await request(searchParams, 'GET', '/api/space/') + return response as AxiosResponse +} + +export async function getSpace( + searchParams: ReturnType, + id: string +): Promise> { + const response = await request(searchParams, 'GET', `/api/space/${id}/`) + return response as AxiosResponse +} diff --git a/desktop-app/renderer/components/custom/copyButton.tsx b/desktop-app/renderer/components/custom/copyButton.tsx new file mode 100644 index 00000000..a05a70b1 --- /dev/null +++ b/desktop-app/renderer/components/custom/copyButton.tsx @@ -0,0 +1,52 @@ +import { CheckIcon, CopyIcon } from '@radix-ui/react-icons' +import * as React from 'react' + +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' + +interface CopyButtonProps extends React.HTMLAttributes { + value: string + src?: string +} + +export async function copyToClipboardWithMeta(value: string, event?: Event) { + navigator.clipboard.writeText(value) +} + +export default function CopyButton({ + value, + className, + src, + ...props +}: CopyButtonProps) { + const [hasCopied, setHasCopied] = React.useState(false) + + React.useEffect(() => { + setTimeout(() => { + setHasCopied(false) + }, 2000) + }, [hasCopied]) + + return ( + + ) +} diff --git a/desktop-app/renderer/components/custom/headerPopover.tsx b/desktop-app/renderer/components/custom/headerPopover.tsx index 92fccdf2..f4b9289e 100644 --- a/desktop-app/renderer/components/custom/headerPopover.tsx +++ b/desktop-app/renderer/components/custom/headerPopover.tsx @@ -19,7 +19,8 @@ import { ToastAction } from '@/components/ui/toast' import { useToast } from '@/components/ui/use-toast' import { addServer, getServers, removeServer } from '@/lib/localStorage' import { standardUrlPartial } from '@/lib/queryParams' -import { PlusCircledIcon } from '@radix-ui/react-icons' +import { PlusIcon } from '@radix-ui/react-icons' +import { Settings } from 'lucide-react' import { useSearchParams } from 'next/navigation' import { useRouter } from 'next/router' import { useState } from 'react' @@ -28,11 +29,16 @@ export default function ServerPopover(): JSX.Element { const { toast } = useToast() const defaultServerName = 'Localhost' const defaultServerURL = 'http://localhost:8000' + const defaulAPIToken = 'xxxxxxxx.xxxxxx.xxxxxxxxx' const [newServerName, setNewServerName] = useState(defaultServerName) const [newServerURL, setNewServerURL] = useState(defaultServerURL) + const [newServerToken, setNewServerToken] = useState(defaulAPIToken) const [servers, setServers] = useState(getServers()) const router = useRouter() const searchParams = useSearchParams() + const currentURL = router.asPath + const urlBase = currentURL.split('?')[0].split('/')[1] + const urlId = currentURL.split('?')[0].split('/')[2] return ( <> @@ -43,40 +49,78 @@ export default function ServerPopover(): JSX.Element {
{Object.entries(servers).map(([key, server], index) => { return ( -
+
) @@ -84,7 +128,7 @@ export default function ServerPopover(): JSX.Element { @@ -111,11 +155,11 @@ export default function ServerPopover(): JSX.Element { />
-
+
+ + { + setNewServerToken(e.currentTarget.value) + }} + /> +
+ ) +} diff --git a/desktop-app/renderer/components/layout/breadcrumb.tsx b/desktop-app/renderer/components/layout/breadcrumb.tsx index efa963cc..9b81e0ae 100644 --- a/desktop-app/renderer/components/layout/breadcrumb.tsx +++ b/desktop-app/renderer/components/layout/breadcrumb.tsx @@ -1,4 +1,8 @@ -import { ExchangeAccount, get } from '@/api/exchangeAccounts/exchangeAccount' +import { + ExchangeAccount, + getExchangeAccount +} from '@/api/exchangeAccounts/exchangeAccount' +import { NapseSpace, getSpace } from '@/api/spaces/spaces' import { Breadcrumb, BreadcrumbItem, @@ -13,7 +17,7 @@ export default function BreadcrumbLayout() { const searchParams = useSearchParams() const serverID = searchParams.get('server') || '' const exchangeAccountID = searchParams.get('exchangeAccount') || '' - const space = searchParams.get('space') || '' + const spaceID = searchParams.get('space') || '' const fleet = searchParams.get('fleet') || '' const bot = searchParams.get('bot') || '' const server = getServer(serverID) @@ -22,7 +26,10 @@ export default function BreadcrumbLayout() { useEffect(() => { const fetchData = async () => { try { - const response = await get(searchParams, exchangeAccountID) + const response = await getExchangeAccount( + searchParams, + exchangeAccountID + ) setExchangeAccount(response.data) } catch (error) { console.error(error) @@ -31,81 +38,96 @@ export default function BreadcrumbLayout() { exchangeAccountID && fetchData() }, [exchangeAccountID, searchParams]) + const [space, setSpace] = useState() + useEffect(() => { + const fetchData = async () => { + try { + const response = await getSpace(searchParams, spaceID) + setSpace(response.data) + } catch (error) { + console.error(error) + } + } + spaceID && fetchData() + }, [spaceID, searchParams]) + return ( -
- - {serverID && ( - - - {server.url} - - - )} - {exchangeAccountID && ( - - - {exchangeAccount?.name} - - - )} - {space && ( - - - {space} - - - )} - {fleet && ( - - - {exchangeAccountID} - - - )} - {bot && ( - - - {exchangeAccountID} - - - )} - +
+
+ + {serverID && ( + + + {server.name} + + + )} + {exchangeAccountID && ( + + + {exchangeAccount?.name} + + + )} + {spaceID && ( + + + {space?.name} + + + )} + {fleet && ( + + + {exchangeAccountID} + + + )} + {bot && ( + + + {exchangeAccountID} + + + )} + +
) } diff --git a/desktop-app/renderer/components/layout/contextHeader.tsx b/desktop-app/renderer/components/layout/contextHeader.tsx index 87540395..a9f95d3f 100644 --- a/desktop-app/renderer/components/layout/contextHeader.tsx +++ b/desktop-app/renderer/components/layout/contextHeader.tsx @@ -10,9 +10,11 @@ import { ThemeButton } from '../custom/themeButton' import BreadcrumbLayout from '@/components/layout/breadcrumb' import { standardUrlPartial } from '@/lib/queryParams' import { useSearchParams } from 'next/navigation' +import { SettingsButton } from '../custom/settingsButton' export default function ContextHeader({ children, + isBreadcrumb = true, isServer = false, isExchangeAccount = false, isSpace = false, @@ -20,6 +22,7 @@ export default function ContextHeader({ isBot = false }: { children: ReactNode + isBreadcrumb?: boolean isServer?: boolean isExchangeAccount?: boolean isSpace?: boolean @@ -49,7 +52,7 @@ export default function ContextHeader({
-
+
{isExchangeAccount && ( {isServer && } +
- + {isBreadcrumb && ( + <> + + {/* */} + + )}
{children}
diff --git a/desktop-app/renderer/components/ui/badge.tsx b/desktop-app/renderer/components/ui/badge.tsx new file mode 100644 index 00000000..5e1555da --- /dev/null +++ b/desktop-app/renderer/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'focus:ring-ring inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2', + { + variants: { + variant: { + default: + 'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent', + secondary: + 'bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent', + destructive: + 'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent', + outline: 'text-foreground' + } + }, + defaultVariants: { + variant: 'default' + } + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/desktop-app/renderer/components/ui/checkbox.tsx b/desktop-app/renderer/components/ui/checkbox.tsx new file mode 100644 index 00000000..c900eca3 --- /dev/null +++ b/desktop-app/renderer/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +'use client' + +import * as CheckboxPrimitive from '@radix-ui/react-checkbox' +import { Check } from 'lucide-react' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/desktop-app/renderer/components/ui/hover-card.tsx b/desktop-app/renderer/components/ui/hover-card.tsx new file mode 100644 index 00000000..d4029755 --- /dev/null +++ b/desktop-app/renderer/components/ui/hover-card.tsx @@ -0,0 +1,29 @@ +'use client' + +import * as HoverCardPrimitive from '@radix-ui/react-hover-card' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const HoverCard = HoverCardPrimitive.Root + +const HoverCardTrigger = HoverCardPrimitive.Trigger + +const HoverCardContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + +)) +HoverCardContent.displayName = HoverCardPrimitive.Content.displayName + +export { HoverCard, HoverCardContent, HoverCardTrigger } diff --git a/desktop-app/renderer/components/ui/switch.tsx b/desktop-app/renderer/components/ui/switch.tsx new file mode 100644 index 00000000..f87ab50f --- /dev/null +++ b/desktop-app/renderer/components/ui/switch.tsx @@ -0,0 +1,29 @@ +'use client' + +import * as SwitchPrimitives from '@radix-ui/react-switch' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/desktop-app/renderer/lib/localStorage.ts b/desktop-app/renderer/lib/localStorage.ts index 93f0571b..c1b916bf 100644 --- a/desktop-app/renderer/lib/localStorage.ts +++ b/desktop-app/renderer/lib/localStorage.ts @@ -2,9 +2,14 @@ interface Server { name: string url: string id: string + token: string } -function addServer(name: string, url: string): number | undefined { +function addServer( + name: string, + url: string, + token: string +): number | undefined { if (typeof window !== 'undefined') { if (!localStorage.servers) { localStorage.servers = JSON.stringify({}) @@ -14,7 +19,7 @@ function addServer(name: string, url: string): number | undefined { while (servers[id]) { id++ } - servers[id] = { name, url } + servers[id] = { name, url, token } localStorage.servers = JSON.stringify(servers) return id } @@ -31,20 +36,35 @@ function removeServer(id: string): void { } } +function updateServer(server: Server) { + if (typeof window !== 'undefined') { + if (!localStorage.servers) { + localStorage.servers = JSON.stringify({}) + } + const servers = JSON.parse(localStorage.servers) + servers[server.id] = { + name: server.name, + url: server.url, + token: server.token + } + localStorage.servers = JSON.stringify(servers) + } +} + function getServer(id: string): Server { if (typeof window !== 'undefined') { if (!localStorage.servers) { localStorage.servers = JSON.stringify({}) } - return ( - { ...JSON.parse(localStorage.servers)[id], id: id.toString() } || { - name: '', - url: '', - id: '' - } - ) + return { + id: id.toString() || '', + name: '', + url: '', + token: '', + ...JSON.parse(localStorage.servers)[id] + } } - return { name: '', url: '', id: '' } + return { name: '', url: '', id: '', token: '' } } function getServers(): Record { @@ -53,12 +73,13 @@ function getServers(): Record { localStorage.servers = JSON.stringify({}) } const parsed = JSON.parse(localStorage.servers) - Object.entries(parsed).map(([key, value], index) => { - parsed[key]['id'] = index.toString() + Object.keys(parsed).map((key) => { + parsed[key]['id'] = key.toString() }) return parsed } return {} } -export { addServer, getServer, getServers, removeServer } +export { addServer, getServer, getServers, removeServer, updateServer } +export type { Server } diff --git a/desktop-app/renderer/lib/queryParams.ts b/desktop-app/renderer/lib/queryParams.ts index 66b1a523..3f5a9589 100644 --- a/desktop-app/renderer/lib/queryParams.ts +++ b/desktop-app/renderer/lib/queryParams.ts @@ -12,7 +12,6 @@ export function buildURLQuery(baseUrl: string, params: Record) { baseUrl = baseUrl + '/' } baseUrl = baseUrl + '?' - const existingParams = baseUrl.split('?')[1] if (existingParams) { params = { diff --git a/desktop-app/renderer/pages/exchangeAccounts/index.tsx b/desktop-app/renderer/pages/exchangeAccounts/index.tsx index 97dcc05c..1c9e7f2e 100644 --- a/desktop-app/renderer/pages/exchangeAccounts/index.tsx +++ b/desktop-app/renderer/pages/exchangeAccounts/index.tsx @@ -1,7 +1,10 @@ import InfoPanelCard from '@/components/custom/panel/infoPanelCard' import ContextHeader from '@/components/layout/contextHeader' import { standardUrlPartial } from '@/lib/queryParams' -import { ExchangeAccount, list } from 'api/exchangeAccounts/exchangeAccount' +import { + ExchangeAccount, + listExchangeAccount +} from 'api/exchangeAccounts/exchangeAccount' import { useSearchParams } from 'next/navigation' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' @@ -15,7 +18,7 @@ export default function ExchangeAccounts(): JSX.Element { useEffect(() => { const fetchData = async () => { try { - const response = await list(searchParams) + const response = await listExchangeAccount(searchParams) setExchangeAccounts(response.data) } catch (error) { console.error(error) diff --git a/desktop-app/renderer/pages/keys/[slug]/index.tsx b/desktop-app/renderer/pages/keys/[slug]/index.tsx new file mode 100644 index 00000000..6d372ca6 --- /dev/null +++ b/desktop-app/renderer/pages/keys/[slug]/index.tsx @@ -0,0 +1,25 @@ +import ContextHeader from '@/components/layout/contextHeader' +import { Server, getServer } from '@/lib/localStorage' +import { useSearchParams } from 'next/navigation' +import { useEffect, useState } from 'react' +export default function Servers(): JSX.Element { + const searchParams = useSearchParams() + const [server, setServer] = useState( + getServer(searchParams.get('server') || '') + ) + useEffect( + () => setServer(getServer(searchParams.get('server') || '')), + [searchParams] + ) + + return ( + +
+

+ Settings - Servers +

+

Here is where you can manage your servers.

+
+
+ ) +} diff --git a/desktop-app/renderer/pages/servers/[slug]/adminFooter.tsx b/desktop-app/renderer/pages/servers/[slug]/adminFooter.tsx new file mode 100644 index 00000000..5c19ac55 --- /dev/null +++ b/desktop-app/renderer/pages/servers/[slug]/adminFooter.tsx @@ -0,0 +1,156 @@ +import { createKey } from '@/api/key/key' +import CopyButton from '@/components/custom/copyButton' +import { Button } from '@/components/ui/button' +import { CardFooter } from '@/components/ui/card' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger +} from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { standardUrlPartial } from '@/lib/queryParams' +import { PlusIcon } from '@radix-ui/react-icons' +import { useSearchParams } from 'next/navigation' +import { useRouter } from 'next/router' +import { useState } from 'react' + +export default function AdminFooter({}): JSX.Element { + const router = useRouter() + const searchParams = useSearchParams() + const defaultAPIKeyName = 'My API Key' + const defaultAPIKeyDescription = 'My API Key Description' + const [APIKeyName, setAPIKeyName] = useState(defaultAPIKeyName) + const [APIKeyDescription, setAPIKeyDescription] = useState( + defaultAPIKeyDescription + ) + const [fullAPIKey, setFullAPIKey] = useState('') + return ( + + + + + + + + Create New API Key + + An API key is a unique identifier used to interact with the + server. You can create a new API key by providing a name and + description. Before you can use it, you then need to give it + Space-Specific permissions. Be careful who you give your API key + to. + + +
+
+ + { + setAPIKeyName(e.currentTarget.value) + }} + /> +
+
+ + { + setAPIKeyDescription(e.currentTarget.value) + }} + /> +
+
+ {fullAPIKey && ( + <> + + Here is the API key you just created. You can copy it and use it + to interact with the server. If you lose it, or leave this page + without copying it, you will need to create a new one. + +
+ +
+ { + setAPIKeyDescription(e.currentTarget.value) + }} + /> + +
+
+ + )} + +
+ + {fullAPIKey && ( + + )} +
+
+
+
+
+ ) +} diff --git a/desktop-app/renderer/pages/servers/[slug]/index.tsx b/desktop-app/renderer/pages/servers/[slug]/index.tsx index 10c328bb..1c2abb3a 100644 --- a/desktop-app/renderer/pages/servers/[slug]/index.tsx +++ b/desktop-app/renderer/pages/servers/[slug]/index.tsx @@ -1,11 +1,31 @@ import ContextHeader from '@/components/layout/contextHeader' -import { useRouter } from 'next/router' - +import { Server, getServer } from '@/lib/localStorage' +import { useSearchParams } from 'next/navigation' +import { useEffect, useState } from 'react' +import SelectedAPIKey from './selectedAPIKey' +import SelectedServer from './selectedServer' export default function Servers(): JSX.Element { - const router = useRouter() + const searchParams = useSearchParams() + const [server, setServer] = useState( + getServer(searchParams.get('server') || '') + ) + useEffect( + () => setServer(getServer(searchParams.get('server') || '')), + [searchParams] + ) + return ( - -

Server ID: {router.query.slug}

+ +
+

+ Settings - Servers +

+

Here is where you can manage your servers.

+
+ + +
+
) } diff --git a/desktop-app/renderer/pages/servers/[slug]/selectedAPIKey.tsx b/desktop-app/renderer/pages/servers/[slug]/selectedAPIKey.tsx new file mode 100644 index 00000000..bbe72fef --- /dev/null +++ b/desktop-app/renderer/pages/servers/[slug]/selectedAPIKey.tsx @@ -0,0 +1,208 @@ +import { Key, getKey, listKey } from '@/api/key/key' +import { Badge } from '@/components/ui/badge' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from '@/components/ui/card' +import { + HoverCard, + HoverCardContent, + HoverCardTrigger +} from '@/components/ui/hover-card' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Switch } from '@/components/ui/switch' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from '@/components/ui/table' +import { Server } from '@/lib/localStorage' +import { standardUrlPartial } from '@/lib/queryParams' +import { AxiosResponse } from 'axios' +import { useSearchParams } from 'next/navigation' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import AdminFooter from './adminFooter' + +const defaultKey: Key = { + name: '', + prefix: '', + permissions: [], + is_master_key: false, + revoked: false, + description: '' +} + +export default function SelectedAPIKey({ + server +}: { + server: Server +}): JSX.Element { + const router = useRouter() + const searchParams = useSearchParams() + const [key, setKey] = useState(defaultKey) + useEffect(() => { + const fetchData = async () => { + try { + const response: AxiosResponse = await getKey( + searchParams, + server.token.split('.')[0] + ) + setKey(response.data) + } catch (error) { + console.error(error) + setKey(defaultKey) + } + } + if (server.token) { + fetchData() + } + }, [server, searchParams]) + const [isAdminView, setIsAdminView] = useState(false) + const [allKeys, setAllKeys] = useState([]) + useEffect(() => { + const fetchData = async () => { + try { + const response: AxiosResponse = await listKey(searchParams) + setAllKeys(response.data) + } catch (error) { + console.error(error) + setAllKeys([]) + } + } + if (server.token) { + fetchData() + } + }, [server, searchParams]) + return ( + + +
+ {isAdminView ? 'Admin View:' : 'Your key:'} + {key.is_master_key && ( + + + { + setIsAdminView(!isAdminView) + }} + /> + + + {isAdminView ? 'Disable Admin View' : 'Enable Admin View'} + + + )} +
+ + {isAdminView + ? 'You manage all distributed API keys here.' + : 'You can view your API key properties here.'} + +
+
+ + {isAdminView ? ( +
+ + + + Name + Prefix + Status + Master + Description + + + + {allKeys.map((key) => ( + { + router + .push( + standardUrlPartial( + '/keys/', + key.prefix, + { + exchangeAccount: '', + space: '', + fleet: '', + bot: '' + }, + searchParams + ) + ) + .catch((err) => { + console.error(err) + }) + }} + > + {key.name} + + {key.prefix} + + + {key.revoked ? 'Revoked' : 'Active'} + + {key.is_master_key ? 'Yes' : 'No'} + + {key.description} + + + ))} + +
+
+ ) : ( +
+
+ + +
+
+ + +
+
+ + +
+ {key.permissions.length > 0 && ( +
+ +
+ {key.permissions.map((permission) => ( + + {permission} + + ))} +
+
+ )} +
+ )} +
+ {isAdminView && } +
+
+ ) +} diff --git a/desktop-app/renderer/pages/servers/[slug]/selectedServer.tsx b/desktop-app/renderer/pages/servers/[slug]/selectedServer.tsx new file mode 100644 index 00000000..808dd3d2 --- /dev/null +++ b/desktop-app/renderer/pages/servers/[slug]/selectedServer.tsx @@ -0,0 +1,122 @@ +import { Button } from '@/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle +} from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { useToast } from '@/components/ui/use-toast' +import { Server, removeServer, updateServer } from '@/lib/localStorage' +import { standardUrlPartial } from '@/lib/queryParams' +import { useSearchParams } from 'next/navigation' +import { useRouter } from 'next/router' +import { Dispatch, SetStateAction } from 'react' + +export default function SelectedServer({ + server, + setServer +}: { + server: Server + setServer: Dispatch> +}): JSX.Element { + const searchParams = useSearchParams() + const router = useRouter() + const { toast } = useToast() + return ( + + + Selected Server: {server.name} + + You can view/edit your server properties here. + + + +
+
+
+ + { + setServer({ ...server, name: e.currentTarget.value }) + }} + /> +
+
+ + { + setServer({ ...server, url: e.currentTarget.value }) + }} + /> +
+
+ + { + setServer({ ...server, token: e.currentTarget.value }) + }} + /> +
+
+
+
+ + + + +
+ ) +} diff --git a/desktop-app/renderer/pages/servers/index.tsx b/desktop-app/renderer/pages/servers/index.tsx index 17ca44cd..1cb26fc4 100644 --- a/desktop-app/renderer/pages/servers/index.tsx +++ b/desktop-app/renderer/pages/servers/index.tsx @@ -2,11 +2,14 @@ import MinimalistPanelCard from '@/components/custom/panel/minimalistPanelCard' import ContextHeader from '@/components/layout/contextHeader' import { getServers } from '@/lib/localStorage' +import { standardUrlPartial } from '@/lib/queryParams' +import { useSearchParams } from 'next/navigation' import { useRouter } from 'next/router' export default function Servers(): JSX.Element { const router = useRouter() const servers = getServers() + const searchParams = useSearchParams() return (
@@ -16,9 +19,24 @@ export default function Servers(): JSX.Element { title={server.name} tooltip={server.url} onClick={() => { - router.push(`/servers/${server.name}`).catch((err) => { - console.error(err) - }) + router + .push( + standardUrlPartial( + '/servers/', + server.id, + { + server: server.id, + exchangeAccount: '', + space: '', + fleet: '', + bot: '' + }, + searchParams + ) + ) + .catch((err) => { + console.error(err) + }) }} /> ))} diff --git a/desktop-app/renderer/pages/settings/index.tsx b/desktop-app/renderer/pages/settings/index.tsx new file mode 100644 index 00000000..1a692c37 --- /dev/null +++ b/desktop-app/renderer/pages/settings/index.tsx @@ -0,0 +1,9 @@ +import Link from 'next/link' + +export default function Servers(): JSX.Element { + return ( + +

Home

+ + ) +} diff --git a/desktop-app/renderer/pages/spaces/[name]/index.tsx b/desktop-app/renderer/pages/spaces/[name]/index.tsx index c6bfaef5..154b35c3 100644 --- a/desktop-app/renderer/pages/spaces/[name]/index.tsx +++ b/desktop-app/renderer/pages/spaces/[name]/index.tsx @@ -1,19 +1,16 @@ import ContextHeader from '@/components/layout/contextHeader' -import React from 'react' -import ValuePanelCard from '@/components/custom/panel/valuePanelCard' +import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { DonutChart, AreaChart } from '@tremor/react' +import { ScrollArea } from '@/components/ui/scroll-area' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Button } from '@/components/ui/button' -import { useState } from 'react' import { useToast } from '@/components/ui/use-toast' -import { ScrollArea } from '@/components/ui/scroll-area' +import { AreaChart, DonutChart } from '@tremor/react' +import React, { useState } from 'react' +import StatCard from '@/components/custom/statCard' import { Currency, getAssetColumns } from './assetsTable/columns' import { AssetsDataTable } from './assetsTable/data-table' -import { Operation, columns } from './transactionHistTable/columns' import { OperationDataTable } from './transactionHistTable/data-table' -import StatCard from '@/components/custom/statCard' const cities = [ { @@ -216,7 +213,7 @@ export default function Spaces(): JSX.Element { Fleets -
+
{spaces.map((space, index) => ( ))}
-
+
-
+
Transaction history @@ -281,7 +278,7 @@ export default function Spaces(): JSX.Element {
- + ([]) + const [spaces, setSpaces] = useState([]) + const searchParams = useSearchParams() useEffect(() => { const fetchData = async () => { try { - const response: AxiosResponse = await axios.get('/space/') + const response: AxiosResponse = + await listSpace(searchParams) setSpaces(response.data) } catch (error) { console.error(error) @@ -28,13 +23,10 @@ export default function Spaces(): JSX.Element { } } fetchData() - }, []) + }, [searchParams]) - const dispatch = useDispatch() const router = useRouter() - // useEffect(() => { - // dispatch(SET_SPACE_NAMES(spaces.map((space) => space.name))) - // }, [spaces, dispatch]) + return (
@@ -46,9 +38,21 @@ export default function Spaces(): JSX.Element { delta={space.delta} // tooltip={space.tooltip} onClick={() => { - router.push(`/spaces/${space.name}`).catch((err) => { - console.error(err) - }) + router + .push( + standardUrlPartial( + '/spaces/', + space.uuid, + { + exchangeAccount: space.exchange_account, + space: space.uuid + }, + searchParams + ) + ) + .catch((err) => { + console.error(err) + }) }} badge={String(space.fleet_count) + ' fleets'} /> diff --git a/desktop-app/yarn.lock b/desktop-app/yarn.lock index 10ea4439..3f65390b 100644 --- a/desktop-app/yarn.lock +++ b/desktop-app/yarn.lock @@ -1582,6 +1582,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-checkbox@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz#98f22c38d5010dd6df4c5744cac74087e3275f4b" + integrity sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-previous" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/react-collapsible@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz#df0e22e7a025439f13f62d4e4a9e92c4a0df5b81" @@ -1693,6 +1708,22 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-hover-card@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-hover-card/-/react-hover-card-1.0.7.tgz#684bca2504432566357e7157e087051aa3577948" + integrity sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-icons@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69" @@ -1888,6 +1919,20 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-switch@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.0.3.tgz#6119f16656a9eafb4424c600fdb36efa5ec5837e" + integrity sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-previous" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/react-tabs@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2"