@orsetra/shared-ui 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/components/layout/index.ts +31 -0
  2. package/components/layout/layout-container.tsx +85 -0
  3. package/components/layout/root-layout-wrapper.tsx +33 -0
  4. package/components/layout/sidebar/data.tsx +88 -0
  5. package/components/layout/sidebar/main-sidebar.tsx +177 -0
  6. package/components/layout/sidebar/sidebar.tsx +750 -0
  7. package/components/layout/skeleton.tsx +15 -0
  8. package/components/ui/accordion.tsx +58 -0
  9. package/components/ui/alert-dialog.tsx +133 -0
  10. package/components/ui/alert.tsx +59 -0
  11. package/components/ui/aspect-ratio.tsx +7 -0
  12. package/components/ui/assets-header.tsx +50 -0
  13. package/components/ui/avatar.tsx +50 -0
  14. package/components/ui/badge.tsx +54 -0
  15. package/components/ui/breadcrumb.tsx +115 -0
  16. package/components/ui/button.tsx +83 -0
  17. package/components/ui/calendar.tsx +66 -0
  18. package/components/ui/card.tsx +79 -0
  19. package/components/ui/carousel.tsx +262 -0
  20. package/components/ui/certificate-editor.tsx +445 -0
  21. package/components/ui/chart.tsx +365 -0
  22. package/components/ui/checkbox.tsx +30 -0
  23. package/components/ui/collapsible.tsx +11 -0
  24. package/components/ui/command.tsx +153 -0
  25. package/components/ui/context-menu.tsx +200 -0
  26. package/components/ui/dialog.tsx +122 -0
  27. package/components/ui/drawer.tsx +118 -0
  28. package/components/ui/dropdown-menu.tsx +200 -0
  29. package/components/ui/environment-settings.tsx +173 -0
  30. package/components/ui/environment-variables-config.tsx +175 -0
  31. package/components/ui/file-import.tsx +177 -0
  32. package/components/ui/form.tsx +178 -0
  33. package/components/ui/hover-card.tsx +29 -0
  34. package/components/ui/index.ts +54 -0
  35. package/components/ui/input-otp.tsx +71 -0
  36. package/components/ui/input.tsx +23 -0
  37. package/components/ui/label.tsx +26 -0
  38. package/components/ui/logo.tsx +17 -0
  39. package/components/ui/menubar.tsx +236 -0
  40. package/components/ui/navigation-menu.tsx +128 -0
  41. package/components/ui/page-header.tsx +35 -0
  42. package/components/ui/pagination.tsx +112 -0
  43. package/components/ui/popover.tsx +31 -0
  44. package/components/ui/process-status.tsx +98 -0
  45. package/components/ui/progress.tsx +31 -0
  46. package/components/ui/radio-group.tsx +44 -0
  47. package/components/ui/resizable.tsx +45 -0
  48. package/components/ui/resource-settings.tsx +227 -0
  49. package/components/ui/scroll-area.tsx +48 -0
  50. package/components/ui/search-input.tsx +26 -0
  51. package/components/ui/secret-explorer.tsx +274 -0
  52. package/components/ui/secret-properties-editor.tsx +642 -0
  53. package/components/ui/select.tsx +162 -0
  54. package/components/ui/selected-asset.tsx +120 -0
  55. package/components/ui/separator.tsx +31 -0
  56. package/components/ui/sheet.tsx +140 -0
  57. package/components/ui/skeleton.tsx +15 -0
  58. package/components/ui/slider.tsx +28 -0
  59. package/components/ui/sonner.tsx +31 -0
  60. package/components/ui/switch.tsx +29 -0
  61. package/components/ui/table.tsx +117 -0
  62. package/components/ui/tabs.tsx +55 -0
  63. package/components/ui/textarea.tsx +22 -0
  64. package/components/ui/toast.tsx +131 -0
  65. package/components/ui/toaster.tsx +35 -0
  66. package/components/ui/toggle-group.tsx +61 -0
  67. package/components/ui/toggle.tsx +45 -0
  68. package/components/ui/tooltip.tsx +30 -0
  69. package/components/ui/user-menu.tsx +86 -0
  70. package/hooks/index.ts +5 -0
  71. package/hooks/use-auth.ts +10 -0
  72. package/hooks/use-mobile.ts +19 -0
  73. package/hooks/use-toast.ts +8 -0
  74. package/hooks/use-websocket.tsx +76 -0
  75. package/index.ts +10 -1
  76. package/package.json +35 -8
@@ -0,0 +1,274 @@
1
+ "use client"
2
+
3
+ import { useState, useEffect } from "react"
4
+ import { Button } from "./button"
5
+ import { Card, CardContent, CardHeader, CardTitle } from "./card"
6
+ import { Key, Plus, Trash2, Folder, File, ChevronRight } from "lucide-react"
7
+ import { useToast } from "../../hooks/use-toast"
8
+
9
+ import type { Secrets } from "@/app/secrets/models"
10
+
11
+ interface SecretExplorerProps {
12
+ currentPath: string
13
+ onPathChange: (path: string) => void
14
+ getSecrets: (path: string) => Promise<Secrets>
15
+ deleteSecret: (path: string) => Promise<boolean>
16
+ selectedSecret: string | null
17
+ onSecretSelect: (secret: string | null) => void
18
+ onCreateClick: () => void
19
+ onRefresh?: () => void
20
+ }
21
+
22
+ export function SecretExplorer({
23
+ currentPath,
24
+ onPathChange,
25
+ getSecrets,
26
+ deleteSecret,
27
+ selectedSecret,
28
+ onSecretSelect,
29
+ onCreateClick,
30
+ onRefresh
31
+ }: SecretExplorerProps) {
32
+ const { toast } = useToast()
33
+ const [secretPaths, setSecretPaths] = useState<string[]>([])
34
+ const [loading, setLoading] = useState(true)
35
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false)
36
+ const [selectedSecretForAction, setSelectedSecretForAction] = useState<string | null>(null)
37
+
38
+ useEffect(() => {
39
+ loadSecrets()
40
+ }, [currentPath])
41
+
42
+ const loadSecrets = async () => {
43
+ try {
44
+ const secretsResponse: Secrets = await getSecrets(currentPath || "/")
45
+ const paths = secretsResponse.data?.keys || []
46
+ setSecretPaths(paths)
47
+ } catch (error) {
48
+ // Navigate up if current path is invalid
49
+ if (currentPath) {
50
+ const pathParts = currentPath.split('/').filter(Boolean)
51
+ pathParts.pop()
52
+ const newPath = pathParts.length > 0 ? pathParts.join('/') + '/' : ""
53
+ onPathChange(newPath)
54
+ }
55
+ toast({
56
+ variant: "destructive",
57
+ title: "Error loading secrets",
58
+ description: "Failed to fetch configuration secrets."
59
+ })
60
+ } finally {
61
+ setLoading(false)
62
+ }
63
+ }
64
+
65
+ const handleItemClick = async (path: string) => {
66
+ if (path.endsWith('/')) {
67
+ // It's a folder, navigate into it
68
+ const newPath = currentPath ? `${currentPath}${path}` : path
69
+ onPathChange(newPath)
70
+ onSecretSelect(null)
71
+ } else {
72
+ // It's a secret, show details
73
+ onSecretSelect(path)
74
+ }
75
+ }
76
+
77
+ const getBreadcrumbs = () => {
78
+ if (!currentPath) return [{ name: "Root", path: "" }]
79
+
80
+ const parts = currentPath.split('/').filter(Boolean)
81
+ const breadcrumbs = [{ name: "Root", path: "" }]
82
+
83
+ let accumulatedPath = ""
84
+ parts.forEach((part, index) => {
85
+ accumulatedPath += part + "/"
86
+ breadcrumbs.push({
87
+ name: part,
88
+ path: accumulatedPath
89
+ })
90
+ })
91
+
92
+ return breadcrumbs
93
+ }
94
+
95
+ const handleDeleteSecret = async () => {
96
+ if (!selectedSecretForAction) return
97
+
98
+ try {
99
+ const fullPath = currentPath ? `${currentPath}${selectedSecretForAction}` : selectedSecretForAction
100
+ await deleteSecret(fullPath)
101
+
102
+ toast({
103
+ variant: "success",
104
+ title: "Secret deleted successfully",
105
+ description: `Configuration "${selectedSecretForAction}" has been deleted.`
106
+ })
107
+
108
+ setShowDeleteDialog(false)
109
+ setSelectedSecretForAction(null)
110
+ loadSecrets()
111
+
112
+ // Clear details if this was the selected secret
113
+ if (selectedSecret === selectedSecretForAction) {
114
+ onSecretSelect(null)
115
+ }
116
+ } catch (error) {
117
+ console.error("Error deleting secret:", error)
118
+ toast({
119
+ variant: "destructive",
120
+ title: "Error deleting secret",
121
+ description: "Failed to delete configuration secret."
122
+ })
123
+ }
124
+ }
125
+
126
+ if (loading) {
127
+ return (
128
+ <Card className="border border-ibm-gray-20">
129
+ <CardHeader className="pb-3">
130
+ <div className="animate-pulse">
131
+ <div className="h-6 bg-ibm-gray-20 mb-2"></div>
132
+ <div className="h-4 bg-ibm-gray-20"></div>
133
+ </div>
134
+ </CardHeader>
135
+ <CardContent className="p-0">
136
+ <div className="animate-pulse">
137
+ <div className="h-32 bg-ibm-gray-20"></div>
138
+ </div>
139
+ </CardContent>
140
+ </Card>
141
+ )
142
+ }
143
+
144
+ return (
145
+ <>
146
+ <Card className="border border-ibm-gray-20">
147
+ <CardHeader className="pb-3">
148
+ <div className="flex items-center justify-between">
149
+ <CardTitle className="text-lg font-medium">Secrets Explorer</CardTitle>
150
+ <Button variant="ghost" size="sm" onClick={onCreateClick}>
151
+ <Plus className="w-4 h-4" />
152
+ </Button>
153
+ </div>
154
+
155
+ {/* Breadcrumbs */}
156
+ <div className="flex items-center gap-1 text-xs text-ibm-gray-70 mt-2">
157
+ {getBreadcrumbs().map((crumb, index) => (
158
+ <div key={crumb.path} className="flex items-center gap-1">
159
+ {index > 0 && <ChevronRight className="w-3 h-3" />}
160
+ <button
161
+ onClick={() => {
162
+ onPathChange(crumb.path)
163
+ onSecretSelect(null)
164
+ }}
165
+ className={`hover:text-ibm-blue-60 ${
166
+ crumb.path === currentPath ? "text-ibm-blue-60 font-medium" : ""
167
+ }`}
168
+ >
169
+ {crumb.name}
170
+ </button>
171
+ </div>
172
+ ))}
173
+ </div>
174
+ </CardHeader>
175
+
176
+ <CardContent className="p-0">
177
+ {/* File List */}
178
+ <div className="border-t border-ibm-gray-20">
179
+ {secretPaths.length === 0 ? (
180
+ <div className="p-8 text-center">
181
+ <Key className="w-8 h-8 text-ibm-gray-30 mx-auto mb-2" />
182
+ <p className="text-sm text-ibm-gray-70">
183
+ Empty folder
184
+ </p>
185
+ </div>
186
+ ) : (
187
+ <div className="divide-y divide-ibm-gray-20">
188
+ {secretPaths.map((path) => {
189
+ const isFolder = path.endsWith('/')
190
+ const displayName = isFolder ? path.slice(0, -1) : path
191
+ const isSelected = selectedSecret === path
192
+
193
+ return (
194
+ <div
195
+ key={path}
196
+ className={`group flex items-center gap-3 px-4 py-3 hover:bg-ibm-gray-10 cursor-pointer transition-colors ${
197
+ isSelected ? "bg-ibm-blue-10 border-r-2 border-ibm-blue-60" : ""
198
+ }`}
199
+ onClick={() => handleItemClick(path)}
200
+ >
201
+ <div className="flex-shrink-0">
202
+ {isFolder ? (
203
+ <Folder className="w-4 h-4 text-ibm-yellow-60" />
204
+ ) : (
205
+ <File className="w-4 h-4 text-ibm-blue-60" />
206
+ )}
207
+ </div>
208
+ <span className="text-sm text-ibm-gray-100 flex-1 truncate">
209
+ {displayName}
210
+ </span>
211
+
212
+ {!isFolder && (
213
+ <Button
214
+ variant="ghost"
215
+ size="xs"
216
+ leftIcon={<Trash2 className="w-3 h-3" />}
217
+ onClick={(e) => {
218
+ e.stopPropagation()
219
+ setSelectedSecretForAction(path)
220
+ setShowDeleteDialog(true)
221
+ }}
222
+ className="opacity-0 group-hover:opacity-100 transition-opacity text-ibm-red-60 hover:text-ibm-red-70 hover:bg-ibm-red-10"
223
+ >
224
+ </Button>
225
+ )}
226
+ </div>
227
+ )
228
+ })}
229
+ </div>
230
+ )}
231
+ </div>
232
+ </CardContent>
233
+ </Card>
234
+
235
+ {/* Delete Confirmation Dialog */}
236
+ <div
237
+ className={`fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 ${
238
+ showDeleteDialog ? 'block' : 'hidden'
239
+ }`}
240
+ onClick={() => setShowDeleteDialog(false)}
241
+ >
242
+ <div
243
+ className="bg-white p-6 rounded-lg max-w-md w-full mx-4"
244
+ onClick={(e) => e.stopPropagation()}
245
+ >
246
+ <h3 className="text-lg font-medium text-ibm-gray-100 mb-2">Delete Secret</h3>
247
+ <p className="text-sm text-ibm-gray-70 mb-6">
248
+ Are you sure you want to delete the secret "{selectedSecretForAction}"?
249
+ This action cannot be undone and will permanently remove all secret data.
250
+ </p>
251
+ <div className="flex justify-end gap-3">
252
+ <Button
253
+ variant="secondary"
254
+ size="sm"
255
+ style={{ borderRadius: 0 }}
256
+ onClick={() => setShowDeleteDialog(false)}
257
+ >
258
+ Cancel
259
+ </Button>
260
+ <Button
261
+ variant="danger"
262
+ style={{ borderRadius: 0 }}
263
+ leftIcon={<Trash2 className="w-3 h-3" />}
264
+ size="sm"
265
+ onClick={handleDeleteSecret}
266
+ >
267
+ Delete Secret
268
+ </Button>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </>
273
+ )
274
+ }