= ({
Reach out to the community
CLI Version: v{data?.currentVersion}
diff --git a/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/SecretVersionsTable.tsx b/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/SecretVersionsTable.tsx
new file mode 100644
index 000000000..2837cacab
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/SecretVersionsTable.tsx
@@ -0,0 +1,85 @@
+import { DataTable } from '@/components/shared/DataTable'
+import type { SecretVersion } from '@/types'
+import { columns } from './columns'
+import { Button } from '@/components/ui/button'
+import { MinusIcon, PlusIcon } from '@heroicons/react/20/solid'
+import { VersionActionDialog } from '../VersionActionDialog'
+import { useState } from 'react'
+import { useSecretsContext } from '../SecretsContext'
+import { useSecret } from '@/lib/hooks/use-secret'
+
+export const SecretVersionsTable = () => {
+ const {
+ selectedSecret,
+ setSelectedVersions,
+ setDialogAction,
+ setDialogOpen,
+ } = useSecretsContext()
+ const { data: secretVersions } = useSecret(selectedSecret?.name)
+
+ if (!selectedSecret || !secretVersions) return null
+
+ return (
+ <>
+ {
+ const versions =
+ Object.keys(selected)
+ .map((s) => {
+ return secretVersions[parseInt(s)]
+ })
+ .filter(Boolean) ?? []
+
+ return (
+
+
+
+
+
+ )
+ }}
+ noResultsChildren={
+
+
No versions found.{' '}
+
+
+ }
+ />
+ >
+ )
+}
diff --git a/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/columns.tsx b/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/columns.tsx
new file mode 100644
index 000000000..135e0b9b4
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/columns.tsx
@@ -0,0 +1,144 @@
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import { copyToClipboard } from '@/lib/utils/copy-to-clipboard'
+import type { SecretVersion } from '@/types'
+import { EllipsisHorizontalIcon } from '@heroicons/react/20/solid'
+import { ArrowsUpDownIcon } from '@heroicons/react/24/outline'
+import type { ColumnDef } from '@tanstack/react-table'
+import { Checkbox } from '@/components/ui/checkbox'
+import { useSecretsContext } from '../SecretsContext'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip'
+import { ScrollArea } from '@/components/ui/scroll-area'
+
+export const columns: ColumnDef[] = [
+ {
+ id: 'select',
+ header: ({ table }) => (
+ table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ />
+ ),
+ cell: ({ row }) => (
+ row.toggleSelected(!!value)}
+ aria-label="Select row"
+ />
+ ),
+ },
+ {
+ accessorKey: 'version',
+ header: 'Version',
+ cell: ({ row }) => {
+ const secretVersion = row.original
+
+ return (
+
+ {secretVersion.version}
+ {secretVersion.latest && (
+
+ Latest
+
+ )}
+
+ )
+ },
+ },
+ {
+ accessorKey: 'value',
+ header: 'Value',
+ cell: ({ row }) => {
+ const secretVersion = row.original
+
+ return (
+
+
+
+
+ {secretVersion.value}
+
+
+
+
+ {secretVersion.value}
+
+
+
+
+ )
+ },
+ },
+ {
+ accessorKey: 'createdAt',
+ header: ({ column }) => {
+ return (
+
+ )
+ },
+ },
+ {
+ accessorKey: 'Actions',
+ cell: ({ row }) => {
+ const secretVersion = row.original
+ const { setDialogAction, setDialogOpen, setSelectedVersions } =
+ useSecretsContext()
+
+ return (
+ <>
+
+
+
+
+
+ Actions
+ copyToClipboard(secretVersion.value)}
+ >
+ Copy secret value
+
+ {
+ setSelectedVersions([secretVersion])
+ setDialogAction('delete')
+ setDialogOpen(true)
+ }}
+ >
+ Delete
+
+
+
+ >
+ )
+ },
+ },
+]
diff --git a/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/index.ts b/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/index.ts
new file mode 100644
index 000000000..c01732dc6
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/index.ts
@@ -0,0 +1,3 @@
+import { SecretVersionsTable } from './SecretVersionsTable'
+
+export default SecretVersionsTable
diff --git a/pkg/dashboard/frontend/src/components/secrets/SecretsContext.tsx b/pkg/dashboard/frontend/src/components/secrets/SecretsContext.tsx
new file mode 100644
index 000000000..bb898e7f8
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/secrets/SecretsContext.tsx
@@ -0,0 +1,56 @@
+import { useSecret } from '@/lib/hooks/use-secret'
+import type { Secret, SecretVersion } from '@/types'
+import React, { createContext, useState, type PropsWithChildren } from 'react'
+import { VersionActionDialog } from './VersionActionDialog'
+
+interface SecretsContextProps {
+ selectedVersions: SecretVersion[]
+ setSelectedVersions: React.Dispatch>
+ selectedSecret?: Secret
+ setSelectedSecret: (secret: Secret | undefined) => void
+ setDialogAction: React.Dispatch>
+ setDialogOpen: React.Dispatch>
+}
+
+export const SecretsContext = createContext({
+ selectedVersions: [],
+ setSelectedVersions: () => {},
+ selectedSecret: undefined,
+ setSelectedSecret: () => {},
+ setDialogAction: () => {},
+ setDialogOpen: () => {},
+})
+
+export const SecretsProvider: React.FC = ({ children }) => {
+ const [selectedSecret, setSelectedSecret] = useState()
+
+ const [selectedVersions, setSelectedVersions] = useState([])
+ const [dialogOpen, setDialogOpen] = useState(false)
+ const [dialogAction, setDialogAction] = useState<'add' | 'delete'>('add')
+
+ return (
+
+ {selectedSecret && (
+
+ )}
+ {children}
+
+ )
+}
+
+export const useSecretsContext = () => {
+ return React.useContext(SecretsContext)
+}
diff --git a/pkg/dashboard/frontend/src/components/secrets/SecretsExplorer.tsx b/pkg/dashboard/frontend/src/components/secrets/SecretsExplorer.tsx
new file mode 100644
index 000000000..96d383977
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/secrets/SecretsExplorer.tsx
@@ -0,0 +1,121 @@
+import { useWebSocket } from '../../lib/hooks/use-web-socket'
+import type { Secret } from '@/types'
+import { Loading } from '../shared'
+
+import AppLayout from '../layout/AppLayout'
+import BreadCrumbs from '../layout/BreadCrumbs'
+import SecretsTreeView from './SecretsTreeView'
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '../ui/select'
+import { useEffect, useState } from 'react'
+import { useSecret } from '@/lib/hooks/use-secret'
+import SecretVersionsTable from './SecretVersionsTable'
+import { SecretsProvider, useSecretsContext } from './SecretsContext'
+
+const SecretsExplorer: React.FC = () => {
+ const { data, loading } = useWebSocket()
+
+ const { selectedSecret, setSelectedSecret } = useSecretsContext()
+
+ useEffect(() => {
+ if (data && data.secrets.length) {
+ setSelectedSecret(data.secrets[0])
+ }
+ }, [data])
+
+ const hasData = Boolean(data && data.secrets.length)
+
+ return (
+
+
+ Secrets
+
+
+ >
+ )
+ }
+ >
+
+ {selectedSecret && hasData ? (
+
+
+
+
+ {hasData && (
+
+ )}
+
+
+
+ Secrets
+
+ {selectedSecret.name}
+
+
+
+
+
+
+ ) : !hasData ? (
+
+ Please refer to our documentation on{' '}
+
+ creating a secret
+ {' '}
+ as we are unable to find any existing secrets.
+
+ ) : null}
+
+
+ )
+}
+
+export default function SecretsExplorerWrapped() {
+ return (
+
+
+
+ )
+}
diff --git a/pkg/dashboard/frontend/src/components/secrets/SecretsTreeView.tsx b/pkg/dashboard/frontend/src/components/secrets/SecretsTreeView.tsx
new file mode 100644
index 000000000..ea7f2900a
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/secrets/SecretsTreeView.tsx
@@ -0,0 +1,66 @@
+import { type FC, useMemo } from 'react'
+import type { Secret } from '@/types'
+import TreeView, { type TreeItemType } from '../shared/TreeView'
+import type { TreeItem, TreeItemIndex } from 'react-complex-tree'
+
+export type SecretsTreeItemType = TreeItemType
+
+interface Props {
+ resources: Secret[]
+ onSelect: (resource: Secret) => void
+ initialItem: Secret
+}
+
+const SecretsTreeView: FC = ({ resources, onSelect, initialItem }) => {
+ const treeItems: Record<
+ TreeItemIndex,
+ TreeItem
+ > = useMemo(() => {
+ const rootItem: TreeItem = {
+ index: 'root',
+ isFolder: true,
+ children: [],
+ data: null,
+ }
+
+ const rootItems: Record> = {
+ root: rootItem,
+ }
+
+ for (const resource of resources) {
+ // add api if not added already
+ if (!rootItems[resource.name]) {
+ rootItems[resource.name] = {
+ index: resource.name,
+ data: {
+ label: resource.name,
+ data: resource,
+ },
+ }
+
+ rootItem.children!.push(resource.name)
+ }
+ }
+
+ return rootItems
+ }, [resources])
+
+ return (
+
+ label={'Secrets'}
+ items={treeItems}
+ initialItem={initialItem.name}
+ getItemTitle={(item) => item.data.label}
+ onPrimaryAction={(items) => {
+ if (items.data.data) {
+ onSelect(items.data.data)
+ }
+ }}
+ renderItemTitle={({ item }) => {
+ return {item.data.label}
+ }}
+ />
+ )
+}
+
+export default SecretsTreeView
diff --git a/pkg/dashboard/frontend/src/components/secrets/VersionActionDialog.tsx b/pkg/dashboard/frontend/src/components/secrets/VersionActionDialog.tsx
new file mode 100644
index 000000000..d2b42fd12
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/secrets/VersionActionDialog.tsx
@@ -0,0 +1,129 @@
+import { Button } from '@/components/ui/button'
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { useSecret } from '@/lib/hooks/use-secret'
+import { Loader2 } from 'lucide-react'
+import { useRef, useState } from 'react'
+import { useSecretsContext } from './SecretsContext'
+import toast from 'react-hot-toast'
+
+interface VersionActionDialogProps {
+ action: 'add' | 'delete'
+ open: boolean
+ setOpen: (open: boolean) => void
+}
+
+export function VersionActionDialog({
+ open,
+ setOpen,
+ action,
+}: VersionActionDialogProps) {
+ const { selectedSecret, selectedVersions } = useSecretsContext()
+ const [loading, setLoading] = useState(false)
+ const [value, setValue] = useState('')
+ const inputRef = useRef(null)
+ const secretName = selectedSecret?.name
+
+ const {
+ addSecretVersion,
+ deleteSecretVersion,
+ mutate: refresh,
+ } = useSecret(secretName)
+
+ const handleSubmit = async () => {
+ setLoading(true)
+
+ if (action === 'add') {
+ if (!value.trim()) {
+ toast.error('Secret value is required')
+ setLoading(false)
+ inputRef.current?.focus()
+ return
+ }
+
+ await addSecretVersion(value)
+ setValue('')
+ } else {
+ if (!selectedVersions) {
+ throw new Error('Selected versions are not provided')
+ }
+
+ await Promise.all([
+ selectedVersions.map((version) => deleteSecretVersion(version)),
+ ])
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, 600))
+ await refresh()
+
+ setOpen(false)
+ setLoading(false)
+ }
+
+ return (
+
+ )
+}
diff --git a/pkg/dashboard/frontend/src/components/shared/DataTable.tsx b/pkg/dashboard/frontend/src/components/shared/DataTable.tsx
new file mode 100644
index 000000000..d068de6e7
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/shared/DataTable.tsx
@@ -0,0 +1,113 @@
+import {
+ type ColumnDef,
+ flexRender,
+ getCoreRowModel,
+ getSortedRowModel,
+ type RowSelectionState,
+ type SortingState,
+ useReactTable,
+} from '@tanstack/react-table'
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table'
+import { useEffect, useState } from 'react'
+import SectionCard from './SectionCard'
+
+interface DataTableProps {
+ columns: ColumnDef[]
+ data: TData[]
+ noResultsChildren?: React.ReactNode
+ headerSiblings?: (state: RowSelectionState) => React.ReactNode
+ title?: string
+}
+
+export function DataTable({
+ columns,
+ data,
+ noResultsChildren = 'No results.',
+ headerSiblings,
+ title,
+}: DataTableProps) {
+ const [sorting, setSorting] = useState([])
+ const [rowSelection, setRowSelection] = useState({})
+
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ onSortingChange: setSorting,
+ getSortedRowModel: getSortedRowModel(),
+ onRowSelectionChange: setRowSelection,
+ state: {
+ sorting,
+ rowSelection,
+ },
+ })
+
+ // reset row selection when data changes
+ useEffect(() => {
+ setRowSelection({})
+ }, [data])
+
+ return (
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ )
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ {noResultsChildren}
+
+
+ )}
+
+
+
+ )
+}
diff --git a/pkg/dashboard/frontend/src/components/shared/SectionCard.tsx b/pkg/dashboard/frontend/src/components/shared/SectionCard.tsx
new file mode 100644
index 000000000..22de68a1b
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/shared/SectionCard.tsx
@@ -0,0 +1,53 @@
+import { cn } from '@/lib/utils'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '../ui/card'
+
+interface SectionCardProps {
+ title?: string
+ description?: string
+ children: React.ReactNode
+ className?: string
+ innerClassName?: string
+ headerClassName?: string
+ headerSiblings?: React.ReactNode
+ footer?: React.ReactNode
+}
+
+const SectionCard = ({
+ title,
+ description,
+ children,
+ className,
+ innerClassName,
+ headerClassName,
+ headerSiblings,
+ footer,
+}: SectionCardProps) => {
+ return (
+
+ {title && (
+
+
+
+ {title}
+
+ {headerSiblings}
+
+ {description && {description}}
+
+ )}
+
+ {children}
+
+ {footer && {footer}}
+
+ )
+}
+
+export default SectionCard
diff --git a/pkg/dashboard/frontend/src/components/shared/Tabs.tsx b/pkg/dashboard/frontend/src/components/shared/Tabs.tsx
index 8874d692c..e838aaa00 100644
--- a/pkg/dashboard/frontend/src/components/shared/Tabs.tsx
+++ b/pkg/dashboard/frontend/src/components/shared/Tabs.tsx
@@ -22,7 +22,7 @@ interface Props {
const Tabs: React.FC = ({ tabs, index, setIndex, round, pill }) => {
return (
-
+
) : !buckets?.length ? (
diff --git a/pkg/dashboard/frontend/src/components/ui/checkbox.tsx b/pkg/dashboard/frontend/src/components/ui/checkbox.tsx
new file mode 100644
index 000000000..57fed414e
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/ui/checkbox.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react'
+import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
+import { Check } from 'lucide-react'
+
+import { cn } from '@/lib/utils/cn'
+
+const Checkbox = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/pkg/dashboard/frontend/src/components/ui/dialog.tsx b/pkg/dashboard/frontend/src/components/ui/dialog.tsx
new file mode 100644
index 000000000..55d6783c7
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/ui/dialog.tsx
@@ -0,0 +1,120 @@
+import * as React from 'react'
+import * as DialogPrimitive from '@radix-ui/react-dialog'
+import { X } from 'lucide-react'
+
+import { cn } from '@/lib/utils/cn'
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = 'DialogHeader'
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = 'DialogFooter'
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/pkg/dashboard/frontend/src/components/ui/table.tsx b/pkg/dashboard/frontend/src/components/ui/table.tsx
new file mode 100644
index 000000000..4cc831988
--- /dev/null
+++ b/pkg/dashboard/frontend/src/components/ui/table.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils/cn'
+
+const Table = React.forwardRef<
+ HTMLTableElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Table.displayName = 'Table'
+
+const TableHeader = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableHeader.displayName = 'TableHeader'
+
+const TableBody = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableBody.displayName = 'TableBody'
+
+const TableFooter = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+ tr]:last:border-b-0',
+ className,
+ )}
+ {...props}
+ />
+))
+TableFooter.displayName = 'TableFooter'
+
+const TableRow = React.forwardRef<
+ HTMLTableRowElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableRow.displayName = 'TableRow'
+
+const TableHead = React.forwardRef<
+ HTMLTableCellElement,
+ React.ThHTMLAttributes
+>(({ className, ...props }, ref) => (
+ |
+))
+TableHead.displayName = 'TableHead'
+
+const TableCell = React.forwardRef<
+ HTMLTableCellElement,
+ React.TdHTMLAttributes
+>(({ className, ...props }, ref) => (
+ |
+))
+TableCell.displayName = 'TableCell'
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableCaption.displayName = 'TableCaption'
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/pkg/dashboard/frontend/src/components/websockets/WSExplorer.tsx b/pkg/dashboard/frontend/src/components/websockets/WSExplorer.tsx
index 7a0dff84f..6807ef2b1 100644
--- a/pkg/dashboard/frontend/src/components/websockets/WSExplorer.tsx
+++ b/pkg/dashboard/frontend/src/components/websockets/WSExplorer.tsx
@@ -36,19 +36,14 @@ import { format } from 'date-fns/format'
import { Input } from '../ui/input'
import { ScrollArea } from '../ui/scroll-area'
import CodeEditor from '../apis/CodeEditor'
-import {
- Card,
- CardContent,
- CardFooter,
- CardHeader,
- CardTitle,
-} from '../ui/card'
+
import { Textarea } from '../ui/textarea'
import useSWRSubscription from 'swr/subscription'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'
import { Badge } from '../ui/badge'
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'
import BreadCrumbs from '../layout/BreadCrumbs'
+import SectionCard from '../shared/SectionCard'
export const LOCAL_STORAGE_KEY = 'nitric-local-dash-api-history'
@@ -418,10 +413,11 @@ const WSExplorer = () => {
-
-
- Messages
-
+
{
Connections: {wsInfo?.connectionCount || 0}
-
-
- setMonitorMessageFilter(evt.target.value)
- }
- />
+ }
+ >
+
+
+ setMonitorMessageFilter(evt.target.value)
+ }
+ />
-
-
-
-
-
- {wsInfo?.messages?.length ? (
-
- {wsInfo.messages
- .filter((message) => {
- let pass = true
-
- if (
- monitorMessageFilter &&
- typeof message.data === 'string'
- ) {
- pass = message.data
- .toLowerCase()
- .includes(
- monitorMessageFilter.toLowerCase(),
- )
- }
-
- return pass
- })
- .map((message, i) => {
- const shouldBeJSON = /^[{[]/.test(
- message.data.trim(),
- )
-
- return (
-
-
-
-
-
-
-
- {message.data}
-
-
- {format(
- new Date(message.time),
- 'HH:mm:ss',
- )}
-
-
-
- {message.data ===
- 'Binary messages are not currently supported by AWS' ? (
-
- Binary messages are not currently
- supported by AWS. Util this is
- supported, use a text-based payload.
-
- ) : (
-
- )}
-
-
-
- )
- })}
-
- ) : (
-
- Send a message to get a response.
-
- )}
-
-
-
-
-
-
-
- Query Params
-
-
-
- {
- setQueryParams(rows)
- }}
- />
-
-
-
-
-
- Message
-
-
- {payloadType === 'text' && (
-
-
-
-
-
-
-
- Messages
-
-
- {connected ? 'Connected' : 'Disconnected'}
-
-
-
- setMessageFilter(evt.target.value)}
- />
-
-
+
+ {wsInfo?.messages?.length ? (
+
-
- Clear Messages
-
-
-
-
-
- {messages.length ? (
-
- {messages
- .filter((message) => {
- let pass = false
-
- if (messageTypeFilter === 'in') {
- pass = message.type === 'message-in'
- } else if (messageTypeFilter === 'out') {
- pass = message.type === 'message-out'
- } else {
- pass = true
- }
-
- if (
- messageFilter &&
- typeof message.data === 'string'
- ) {
- pass = message.data
- .toLowerCase()
- .includes(messageFilter.toLowerCase())
- }
-
- return pass
- })
- .map((message, i) => {
- const shouldBeJSON = /^[{[]/.test(
- message.data.trim(),
- )
-
- return (
-
-
-
-
-
-
-
- {message.data}
-
-
- {format(
- new Date(message.ts),
- 'HH:mm:ss',
- )}
-
-
-
+ {wsInfo.messages
+ .filter((message) => {
+ let pass = true
+
+ if (
+ monitorMessageFilter &&
+ typeof message.data === 'string'
+ ) {
+ pass = message.data
+ .toLowerCase()
+ .includes(monitorMessageFilter.toLowerCase())
+ }
+
+ return pass
+ })
+ .map((message, i) => {
+ const shouldBeJSON = /^[{[]/.test(
+ message.data.trim(),
+ )
+
+ return (
+
+
+
+
+
+
+
+ {message.data}
+
+
+ {format(
+ new Date(message.time),
+ 'HH:mm:ss',
+ )}
+
+
+
+ {message.data ===
+ 'Binary messages are not currently supported by AWS' ? (
+
+ Binary messages are not currently
+ supported by AWS. Util this is
+ supported, use a text-based payload.
+
+ ) : (
{
height="208px"
className="h-52"
/>
-
-
-
- )
- })}
-
- ) : (
-
- Send a message to get a response.
-
- )}
+ )}
+
+
+
+ )
+ })}
+
+ ) : (
+
+ Send a message to get a response.
+
+ )}
+
+
+
+
+
+
+ {
+ setQueryParams(rows)
+ }}
+ />
+
+
+
+
+
+
+ >
+ }
+ >
+ {payloadType === 'text' && (
+
+
+
+
+ {connected ? 'Connected' : 'Disconnected'}
+
-
-
+ }
+ >
+
+ setMessageFilter(evt.target.value)}
+ />
+
+
+
+
+ {messages.length ? (
+
+ {messages
+ .filter((message) => {
+ let pass = false
+
+ if (messageTypeFilter === 'in') {
+ pass = message.type === 'message-in'
+ } else if (messageTypeFilter === 'out') {
+ pass = message.type === 'message-out'
+ } else {
+ pass = true
+ }
+
+ if (
+ messageFilter &&
+ typeof message.data === 'string'
+ ) {
+ pass = message.data
+ .toLowerCase()
+ .includes(messageFilter.toLowerCase())
+ }
+
+ return pass
+ })
+ .map((message, i) => {
+ const shouldBeJSON = /^[{[]/.test(
+ message.data.trim(),
+ )
+
+ return (
+
+
+
+
+
+
+
+ {message.data}
+
+
+ {format(
+ new Date(message.ts),
+ 'HH:mm:ss',
+ )}
+
+
+
+
+
+
+
+ )
+ })}
+
+ ) : (
+
+ Send a message to get a response.
+
+ )}
+
+
diff --git a/pkg/dashboard/frontend/src/lib/constants.ts b/pkg/dashboard/frontend/src/lib/constants.ts
index 96196b55c..0234ea5a1 100644
--- a/pkg/dashboard/frontend/src/lib/constants.ts
+++ b/pkg/dashboard/frontend/src/lib/constants.ts
@@ -2,6 +2,10 @@ import { getHost } from './utils'
export const STORAGE_API = `http://${getHost()}/api/storage`
+export const SQL_API = `http://${getHost()}/api/sql`
+
+export const SECRETS_API = `http://${getHost()}/api/secrets`
+
export const TABLE_QUERY = `
SELECT
tbl.schemaname AS schema_name,
diff --git a/pkg/dashboard/frontend/src/lib/hooks/use-secret.ts b/pkg/dashboard/frontend/src/lib/hooks/use-secret.ts
new file mode 100644
index 000000000..90288e0fb
--- /dev/null
+++ b/pkg/dashboard/frontend/src/lib/hooks/use-secret.ts
@@ -0,0 +1,47 @@
+import { useCallback } from 'react'
+import useSWR from 'swr'
+import { fetcher } from './fetcher'
+import type { SecretVersion } from '@/types'
+import { SECRETS_API } from '../constants'
+
+export const useSecret = (secretName?: string) => {
+ const { data, mutate } = useSWR(
+ secretName
+ ? `${SECRETS_API}?action=list-versions&secret=${secretName}`
+ : null,
+ fetcher(),
+ )
+
+ const addSecretVersion = useCallback(
+ async (value: string) => {
+ return fetch(
+ `${SECRETS_API}?action=add-secret-version&secret=${secretName}`,
+ {
+ method: 'POST',
+ body: JSON.stringify({ value }),
+ },
+ )
+ },
+ [secretName],
+ )
+
+ const deleteSecretVersion = useCallback(
+ async (sv: SecretVersion) => {
+ return fetch(
+ `${SECRETS_API}?action=delete-secret&secret=${secretName}&version=${sv.version}&latest=${sv.latest}`,
+ {
+ method: 'DELETE',
+ },
+ )
+ },
+ [secretName],
+ )
+
+ return {
+ data,
+ mutate,
+ addSecretVersion,
+ deleteSecretVersion,
+ loading: !data,
+ }
+}
diff --git a/pkg/dashboard/frontend/src/lib/hooks/use-sql-meta.ts b/pkg/dashboard/frontend/src/lib/hooks/use-sql-meta.ts
index adf57a728..13da7d6f2 100644
--- a/pkg/dashboard/frontend/src/lib/hooks/use-sql-meta.ts
+++ b/pkg/dashboard/frontend/src/lib/hooks/use-sql-meta.ts
@@ -1,7 +1,6 @@
import useSWR from 'swr'
import { fetcher } from './fetcher'
-import { TABLE_QUERY } from '../constants'
-import { getHost } from '../utils'
+import { SQL_API, TABLE_QUERY } from '../constants'
export interface SqlMetaResult {
columns: {
@@ -17,7 +16,7 @@ export interface SqlMetaResult {
export const useSqlMeta = (connectionString?: string) => {
const { data, mutate } = useSWR(
- connectionString ? `http://${getHost()}/api/sql` : null,
+ connectionString ? SQL_API : null,
fetcher({
method: 'POST',
body: JSON.stringify({ query: TABLE_QUERY, connectionString }),
diff --git a/pkg/dashboard/frontend/src/lib/utils/generate-architecture-data.ts b/pkg/dashboard/frontend/src/lib/utils/generate-architecture-data.ts
index 17a235bff..aae2d78ba 100644
--- a/pkg/dashboard/frontend/src/lib/utils/generate-architecture-data.ts
+++ b/pkg/dashboard/frontend/src/lib/utils/generate-architecture-data.ts
@@ -17,6 +17,7 @@ import {
GlobeAltIcon,
ArrowsRightLeftIcon,
QueueListIcon,
+ LockClosedIcon,
} from '@heroicons/react/24/outline'
import {
MarkerType,
@@ -54,6 +55,7 @@ import { QueueNode } from '@/components/architecture/nodes/QueueNode'
import { SQLNode } from '@/components/architecture/nodes/SQLNode'
import { SiPostgresql } from 'react-icons/si'
import { unique } from 'radash'
+import { SecretNode } from '@/components/architecture/nodes/SecretNode'
export const nodeTypes = {
api: APINode,
@@ -66,6 +68,7 @@ export const nodeTypes = {
sql: SQLNode,
httpproxy: HttpProxyNode,
queue: QueueNode,
+ secret: SecretNode,
}
const createNode = (
@@ -178,6 +181,7 @@ const actionVerbs = [
'Write',
'Enqueue',
'Dequeue',
+ 'Access',
]
function verbFromNitricAction(action: string) {
@@ -326,6 +330,16 @@ export function generateArchitectureData(data: WebSocketResponse): {
nodes.push(node)
})
+ data.secrets.forEach((secret) => {
+ const node = createNode(secret, 'secret', {
+ title: secret.name,
+ resource: secret,
+ icon: LockClosedIcon,
+ })
+
+ nodes.push(node)
+ })
+
data.sqlDatabases.forEach((sql) => {
const node = createNode(sql, 'sql', {
title: sql.name,
diff --git a/pkg/dashboard/frontend/src/pages/secrets.astro b/pkg/dashboard/frontend/src/pages/secrets.astro
new file mode 100644
index 000000000..7565d97ce
--- /dev/null
+++ b/pkg/dashboard/frontend/src/pages/secrets.astro
@@ -0,0 +1,8 @@
+---
+import SecretsExplorer from "@/components/secrets/SecretsExplorer";
+import Layout from "@/layouts/Layout.astro";
+---
+
+
+
+
diff --git a/pkg/dashboard/frontend/src/types.ts b/pkg/dashboard/frontend/src/types.ts
index 137def04b..203043122 100644
--- a/pkg/dashboard/frontend/src/types.ts
+++ b/pkg/dashboard/frontend/src/types.ts
@@ -73,6 +73,15 @@ export type Bucket = BaseResource
export type Queue = BaseResource
+export type Secret = BaseResource
+
+export interface SecretVersion {
+ version: string
+ value: string
+ createdAt: string
+ latest: boolean
+}
+
type ResourceType = 'bucket' | 'topic' | 'websocket' | 'kv' | 'secret' | 'queue'
export type Notification = {
@@ -104,6 +113,7 @@ export interface WebSocketResponse {
topics: Topic[]
services: Service[]
stores: KeyValue[]
+ secrets: Secret[]
sqlDatabases: SQLDatabase[]
httpProxies: HttpProxy[]
websockets: WebSocket[]
@@ -115,11 +125,10 @@ export interface WebSocketResponse {
apiAddresses: Record
websocketAddresses: Record
httpWorkerAddresses: Record
- storageAddress: string // has http:// prefix
+ storageAddress: string
currentVersion: string
latestVersion: string
connected: boolean
- dashboardAddress: string
}
export interface Param {
diff --git a/pkg/dashboard/frontend/test-app/services/my-test-secret.ts b/pkg/dashboard/frontend/test-app/services/my-test-secret.ts
new file mode 100644
index 000000000..d0e12aa13
--- /dev/null
+++ b/pkg/dashboard/frontend/test-app/services/my-test-secret.ts
@@ -0,0 +1,34 @@
+import { api, secret } from '@nitric/sdk'
+
+const mySecret = secret('my-first-secret').allow('access', 'put')
+
+const mySecondSecret = secret('my-second-secret').allow('access', 'put')
+
+const shhApi = api('my-secret-api')
+
+shhApi.get('/get', async (ctx) => {
+ const latestValue = await mySecret.latest().access()
+
+ ctx.res.body = latestValue.asString()
+
+ return ctx
+})
+
+shhApi.post('/set', async (ctx) => {
+ const data = ctx.req.json()
+
+ await mySecret.put(JSON.stringify(data))
+
+ return ctx
+})
+
+shhApi.post('/set-binary', async (ctx) => {
+ const data = new Uint8Array(1024)
+ for (let i = 0; i < data.length; i++) {
+ data[i] = i % 256
+ }
+
+ await mySecret.put(data)
+
+ return ctx
+})
diff --git a/pkg/dashboard/frontend/yarn.lock b/pkg/dashboard/frontend/yarn.lock
index cb4c8c608..438729ff8 100644
--- a/pkg/dashboard/frontend/yarn.lock
+++ b/pkg/dashboard/frontend/yarn.lock
@@ -20,10 +20,10 @@
resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-1.8.2.tgz#f305d5724c45a9932a8ef4e87b2e7227d15d1c2b"
integrity sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==
-"@astrojs/compiler@^2.9.0":
- version "2.9.2"
- resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.9.2.tgz#c01dcdb14dc938758e66f9856934b83b6cd986d1"
- integrity sha512-Vpu0Ffsj8SoV+N0DFHlxxOMKHwSC9059Xy/OlG1t6uFYSoJXxkBC2WyF6igO7x10V+8uJrhOxaXr3nA90kJXow==
+"@astrojs/compiler@^2.10.0":
+ version "2.10.1"
+ resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.10.1.tgz#36eb9dcf55f0ddc774e245eadc8c1132caac58fd"
+ integrity sha512-XmM4j6BjvOVMag2xELq0JuG2yKOW8wgIu6dvb9BsjbGYmnvoStJn/pqEzVqc1EBszf2xYT7onIkftIOUz9AwrQ==
"@astrojs/internal-helpers@0.4.1":
version "0.4.1"
@@ -61,10 +61,10 @@
dependencies:
prismjs "^1.29.0"
-"@astrojs/react@^3.6.0":
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/@astrojs/react/-/react-3.6.0.tgz#15ffd7a0f21fc37c7661c2ed3911794c1f1d0962"
- integrity sha512-YGLxy5jCU9xKG/HAvYsWMcvrQVIhqVe0Sda3Z5UtP32rfXeG6B9J1xQvnx+kRSFTpIrj+7AwPSDSehLbCHJ56w==
+"@astrojs/react@^3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@astrojs/react/-/react-3.6.1.tgz#c2f7f61b8681a386dc9d7a369a248ea317e2114a"
+ integrity sha512-Mpgu+GYTba1TEzLMshojeLxi7y/FqrTNtpGTCvI366bRbnCkqtvznRj/xPQOTybwXnYXSgxsELyUvqD51iFeiw==
dependencies:
"@vitejs/plugin-react" "^4.3.1"
ultrahtml "^1.5.3"
@@ -118,7 +118,12 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.0.tgz#6b226a5da3a686db3c30519750e071dce292ad95"
integrity sha512-P4fwKI2mjEb3ZU5cnMJzvRsRKGBUcs8jvxIoRmr6ufAY9Xk2Bz7JubRTTivkw55c7WQJfTECeqYVa+HZ0FzREg==
-"@babel/core@^7.24.5", "@babel/core@^7.24.9":
+"@babel/compat-data@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5"
+ integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==
+
+"@babel/core@^7.24.5":
version "7.24.9"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82"
integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==
@@ -139,6 +144,27 @@
json5 "^2.2.3"
semver "^6.3.1"
+"@babel/core@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77"
+ integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.24.7"
+ "@babel/generator" "^7.25.0"
+ "@babel/helper-compilation-targets" "^7.25.2"
+ "@babel/helper-module-transforms" "^7.25.2"
+ "@babel/helpers" "^7.25.0"
+ "@babel/parser" "^7.25.0"
+ "@babel/template" "^7.25.0"
+ "@babel/traverse" "^7.25.2"
+ "@babel/types" "^7.25.2"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
"@babel/generator@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc"
@@ -149,7 +175,7 @@
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
-"@babel/generator@^7.24.10", "@babel/generator@^7.24.9", "@babel/generator@^7.25.0":
+"@babel/generator@^7.24.9", "@babel/generator@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e"
integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==
@@ -184,6 +210,17 @@
lru-cache "^5.1.1"
semver "^6.3.1"
+"@babel/helper-compilation-targets@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c"
+ integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==
+ dependencies:
+ "@babel/compat-data" "^7.25.2"
+ "@babel/helper-validator-option" "^7.24.8"
+ browserslist "^4.23.1"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
"@babel/helper-environment-visitor@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
@@ -229,7 +266,17 @@
"@babel/helper-validator-identifier" "^7.24.7"
"@babel/traverse" "^7.25.0"
-"@babel/helper-plugin-utils@^7.24.7":
+"@babel/helper-module-transforms@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6"
+ integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.24.7"
+ "@babel/helper-simple-access" "^7.24.7"
+ "@babel/helper-validator-identifier" "^7.24.7"
+ "@babel/traverse" "^7.25.2"
+
+"@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
@@ -274,7 +321,7 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d"
integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==
-"@babel/helpers@^7.24.8":
+"@babel/helpers@^7.24.8", "@babel/helpers@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a"
integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==
@@ -316,6 +363,13 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad"
integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==
+"@babel/parser@^7.25.3":
+ version "7.25.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065"
+ integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==
+ dependencies:
+ "@babel/types" "^7.25.2"
+
"@babel/plugin-syntax-jsx@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d"
@@ -337,16 +391,16 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
-"@babel/plugin-transform-react-jsx@^7.24.7":
- version "7.24.7"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz#17cd06b75a9f0e2bd076503400e7c4b99beedac4"
- integrity sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==
+"@babel/plugin-transform-react-jsx@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz#e37e8ebfa77e9f0b16ba07fadcb6adb47412227a"
+ integrity sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-module-imports" "^7.24.7"
- "@babel/helper-plugin-utils" "^7.24.7"
+ "@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-syntax-jsx" "^7.24.7"
- "@babel/types" "^7.24.7"
+ "@babel/types" "^7.25.2"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.21.0"
@@ -393,6 +447,19 @@
debug "^4.3.1"
globals "^11.1.0"
+"@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3":
+ version "7.25.3"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490"
+ integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==
+ dependencies:
+ "@babel/code-frame" "^7.24.7"
+ "@babel/generator" "^7.25.0"
+ "@babel/parser" "^7.25.3"
+ "@babel/template" "^7.25.0"
+ "@babel/types" "^7.25.2"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
"@babel/traverse@^7.4.5":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
@@ -436,6 +503,15 @@
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125"
+ integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==
+ dependencies:
+ "@babel/helper-string-parser" "^7.24.8"
+ "@babel/helper-validator-identifier" "^7.24.7"
+ to-fast-properties "^2.0.0"
+
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.5.0.tgz#1e6c34b624595bf982e8cb18110ae88fce3d6ebd"
@@ -1383,16 +1459,11 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
-"@jridgewell/sourcemap-codec@^1.4.14":
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
-"@jridgewell/sourcemap-codec@^1.4.15":
- version "1.4.15"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
- integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
-
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.17"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
@@ -1732,6 +1803,20 @@
dependencies:
"@radix-ui/react-primitive" "2.0.0"
+"@radix-ui/react-checkbox@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz#a559c4303957d797acee99914480b755aa1f27d6"
+ integrity sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-presence" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+ "@radix-ui/react-use-previous" "1.1.0"
+ "@radix-ui/react-use-size" "1.1.0"
+
"@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"
@@ -1804,7 +1889,7 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==
-"@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.0.5":
+"@radix-ui/react-dialog@^1.0.4":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300"
integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==
@@ -1825,6 +1910,26 @@
aria-hidden "^1.1.1"
react-remove-scroll "2.5.5"
+"@radix-ui/react-dialog@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44"
+ integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-dismissable-layer" "1.1.0"
+ "@radix-ui/react-focus-guards" "1.1.0"
+ "@radix-ui/react-focus-scope" "1.1.0"
+ "@radix-ui/react-id" "1.1.0"
+ "@radix-ui/react-portal" "1.1.1"
+ "@radix-ui/react-presence" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-slot" "1.1.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "2.5.7"
+
"@radix-ui/react-direction@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b"
@@ -2334,6 +2439,11 @@
dependencies:
"@babel/runtime" "^7.13.10"
+"@radix-ui/react-use-previous@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c"
+ integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==
+
"@radix-ui/react-use-rect@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2"
@@ -2584,6 +2694,13 @@
dependencies:
"@types/hast" "^3.0.4"
+"@shikijs/core@1.12.1":
+ version "1.12.1"
+ resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.12.1.tgz#32626494bef573cce01f9e0a36d5776cbc1b2e58"
+ integrity sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==
+ dependencies:
+ "@types/hast" "^3.0.4"
+
"@tailwindcss/forms@^0.5.7":
version "0.5.7"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.7.tgz#db5421f062a757b5f828bc9286ba626c6685e821"
@@ -2591,6 +2708,18 @@
dependencies:
mini-svg-data-uri "^1.2.3"
+"@tanstack/react-table@^8.20.1":
+ version "8.20.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.20.1.tgz#bd2d549d8a18458fb8284025ce66a9b86176fa6b"
+ integrity sha512-PJK+07qbengObe5l7c8vCdtefXm8cyR4i078acWrHbdm8JKw1ES7YpmOtVt9ALUVEEFAHscdVpGRhRgikgFMbQ==
+ dependencies:
+ "@tanstack/table-core" "8.20.1"
+
+"@tanstack/table-core@8.20.1":
+ version "8.20.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.20.1.tgz#74bfab10fa35bed51fa0bd2f3539a331d7e78f1b"
+ integrity sha512-5Ly5TIRHnWH7vSDell9B/OVyV380qqIJVg7H7R7jU4fPEmOD4smqAX7VRflpYI09srWR8aj5OLD2Ccs1pI5mTg==
+
"@types/babel__core@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -3446,34 +3575,33 @@ astro-fathom@^2.0.0:
resolved "https://registry.yarnpkg.com/astro-fathom/-/astro-fathom-2.0.0.tgz#cb2baa229114079900dc39a340ed70c6dfcb44fe"
integrity sha512-Z0UXgAKrfJ43IRPhPzoXWOapVXF250AefvLaphsJViok551UpzIIDuxkUYX2CBXDtqIhH54KYPDaA9V9XyclgA==
-astro@^4.12.2:
- version "4.12.2"
- resolved "https://registry.yarnpkg.com/astro/-/astro-4.12.2.tgz#c92bec880e05577997dd963a423f0ad54e7d6cf4"
- integrity sha512-l6OmqlL+FiuSi9x6F+EGZitteOznq1JffOil7st7cdqeMCTEIym4oagI1a6zp6QekliKWEEZWdplGhgh1k1f7Q==
+astro@^4.13.1:
+ version "4.13.1"
+ resolved "https://registry.yarnpkg.com/astro/-/astro-4.13.1.tgz#cb8f5e7763284f9d6829b8414c00bbdc6d8ca9c8"
+ integrity sha512-VnMjAc+ykFsIVjgbu9Mt/EA1fMIcPMXbU89h3ATwGOzSIKDsQH72bDgfJkWiwk6u0OE90GeP5EPhAT28Twf9oA==
dependencies:
- "@astrojs/compiler" "^2.9.0"
+ "@astrojs/compiler" "^2.10.0"
"@astrojs/internal-helpers" "0.4.1"
"@astrojs/markdown-remark" "5.2.0"
"@astrojs/telemetry" "3.1.0"
- "@babel/core" "^7.24.9"
- "@babel/generator" "^7.24.10"
- "@babel/parser" "^7.24.8"
- "@babel/plugin-transform-react-jsx" "^7.24.7"
- "@babel/traverse" "^7.24.8"
- "@babel/types" "^7.24.9"
+ "@babel/core" "^7.25.2"
+ "@babel/generator" "^7.25.0"
+ "@babel/parser" "^7.25.3"
+ "@babel/plugin-transform-react-jsx" "^7.25.2"
+ "@babel/traverse" "^7.25.3"
+ "@babel/types" "^7.25.2"
"@types/babel__core" "^7.20.5"
"@types/cookie" "^0.6.0"
acorn "^8.12.1"
aria-query "^5.3.0"
axobject-query "^4.1.0"
boxen "7.1.1"
- chokidar "^3.6.0"
ci-info "^4.0.0"
clsx "^2.1.1"
common-ancestor-path "^1.0.1"
cookie "^0.6.0"
cssesc "^3.0.0"
- debug "^4.3.5"
+ debug "^4.3.6"
deterministic-object-hash "^2.0.2"
devalue "^5.0.0"
diff "^5.2.0"
@@ -3491,7 +3619,7 @@ astro@^4.12.2:
http-cache-semantics "^4.1.1"
js-yaml "^4.1.0"
kleur "^4.1.5"
- magic-string "^0.30.10"
+ magic-string "^0.30.11"
mrmime "^2.0.0"
ora "^8.0.1"
p-limit "^6.1.0"
@@ -3500,19 +3628,19 @@ astro@^4.12.2:
preferred-pm "^4.0.0"
prompts "^2.4.2"
rehype "^13.0.1"
- semver "^7.6.2"
- shiki "^1.10.3"
+ semver "^7.6.3"
+ shiki "^1.12.0"
string-width "^7.2.0"
strip-ansi "^7.1.0"
tsconfck "^3.1.1"
unist-util-visit "^5.0.0"
vfile "^6.0.2"
- vite "^5.3.4"
+ vite "^5.3.5"
vitefu "^0.2.5"
which-pm "^3.0.0"
yargs-parser "^21.1.1"
zod "^3.23.8"
- zod-to-json-schema "^3.23.1"
+ zod-to-json-schema "^3.23.2"
optionalDependencies:
sharp "^0.33.3"
@@ -3810,21 +3938,6 @@ chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
-chokidar@^3.6.0:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
- integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
- dependencies:
- anymatch "~3.1.2"
- braces "~3.0.2"
- glob-parent "~5.1.2"
- is-binary-path "~2.1.0"
- is-glob "~4.0.1"
- normalize-path "~3.0.0"
- readdirp "~3.6.0"
- optionalDependencies:
- fsevents "~2.3.2"
-
chonky-icon-fontawesome@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/chonky-icon-fontawesome/-/chonky-icon-fontawesome-2.3.2.tgz#53347e3f63e0351f875fe6946f22284f2ed7905d"
@@ -4310,7 +4423,7 @@ debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3
dependencies:
ms "2.1.2"
-debug@^4.3.5:
+debug@^4.3.6:
version "4.3.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b"
integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
@@ -6289,12 +6402,12 @@ lucide-react@^0.261.0:
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.261.0.tgz#e3aa94d45c13d29d6302e9a50935dc84a4f5dd67"
integrity sha512-gzxEvIxf8+hGbm2ZQU/VP5TxTcnTu3ODDmYoS3a53wC4gkW9ukxmexKjTcZUzz3q8ema+DMwmPypx9Z0Bvvxog==
-magic-string@^0.30.10:
- version "0.30.10"
- resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
- integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
+magic-string@^0.30.11:
+ version "0.30.11"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954"
+ integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==
dependencies:
- "@jridgewell/sourcemap-codec" "^1.4.15"
+ "@jridgewell/sourcemap-codec" "^1.5.0"
markdown-table@^3.0.0:
version "3.0.3"
@@ -7965,7 +8078,7 @@ semver@^7.5.3:
dependencies:
lru-cache "^6.0.0"
-semver@^7.6.0, semver@^7.6.2:
+semver@^7.6.0, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@@ -8029,6 +8142,14 @@ shiki@^1.10.3:
"@shikijs/core" "1.12.0"
"@types/hast" "^3.0.4"
+shiki@^1.12.0:
+ version "1.12.1"
+ resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.12.1.tgz#72d9d230a8d68ddaf8cf7c94a1dc6a5f2625324e"
+ integrity sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==
+ dependencies:
+ "@shikijs/core" "1.12.1"
+ "@types/hast" "^3.0.4"
+
shortid@^2.2.16:
version "2.2.16"
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608"
@@ -8795,7 +8916,7 @@ vfile@^6.0.2:
unist-util-stringify-position "^4.0.0"
vfile-message "^4.0.0"
-vite@^5.3.4:
+vite@^5.3.5:
version "5.3.5"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8"
integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==
@@ -8960,7 +9081,7 @@ yocto-queue@^1.1.1:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
-zod-to-json-schema@^3.23.1:
+zod-to-json-schema@^3.23.2:
version "3.23.2"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz#bc7e379c8050462538383e382964c03d8fe008f9"
integrity sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==
diff --git a/pkg/dashboard/handlers.go b/pkg/dashboard/handlers.go
index 177fc6cd8..2758e3fae 100644
--- a/pkg/dashboard/handlers.go
+++ b/pkg/dashboard/handlers.go
@@ -37,6 +37,7 @@ import (
"github.com/nitrictech/cli/pkg/cloud/websockets"
base_http "github.com/nitrictech/nitric/cloud/common/runtime/gateway"
apispb "github.com/nitrictech/nitric/core/pkg/proto/apis/v1"
+ secretspb "github.com/nitrictech/nitric/core/pkg/proto/secrets/v1"
storagepb "github.com/nitrictech/nitric/core/pkg/proto/storage/v1"
)
@@ -285,6 +286,99 @@ func (d *Dashboard) createSqlQueryHandler() func(http.ResponseWriter, *http.Requ
}
}
+func (d *Dashboard) createSecretsHandler() func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
+ w.Header().Set("Access-Control-Allow-Headers", "*")
+
+ if r.Method == "OPTIONS" {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ secretName := r.URL.Query().Get("secret")
+ action := r.URL.Query().Get("action")
+ version := r.URL.Query().Get("version")
+ latest := r.URL.Query().Get("latest")
+
+ if secretName == "" {
+ http.Error(w, "missing secret param", http.StatusBadRequest)
+ return
+ }
+
+ switch action {
+ case "list-versions":
+ secretVersions, err := d.secretService.List(context.Background(), secretName)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ jsonResponse, err := json.Marshal(secretVersions)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ _, err = w.Write(jsonResponse)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ case "add-secret-version":
+ // get data from body
+ var requestBody struct {
+ Value string `json:"value"`
+ }
+
+ err := json.NewDecoder(r.Body).Decode(&requestBody)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ if requestBody.Value == "" {
+ http.Error(w, "missing value param", http.StatusBadRequest)
+ return
+ }
+
+ _, err = d.secretService.Put(context.Background(), &secretspb.SecretPutRequest{
+ Secret: &secretspb.Secret{
+ Name: secretName,
+ },
+ Value: []byte(requestBody.Value),
+ })
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ case "delete-secret":
+ if version == "" {
+ http.Error(w, "missing version param", http.StatusBadRequest)
+ return
+ }
+
+ err := d.secretService.Delete(context.Background(), secretName, version, latest == "true")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ default:
+ http.Error(w, "invalid action", http.StatusBadRequest)
+ return
+ }
+ }
+}
+
func (d *Dashboard) createHistoryHttpHandler() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")