insforge 0.3.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 (395) hide show
  1. package/.dockerignore +58 -0
  2. package/.env.example +49 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
  6. package/.github/copilot-instructions.md +147 -0
  7. package/.github/workflows/build-image.yml +65 -0
  8. package/.github/workflows/ci-premerge-check.yml +24 -0
  9. package/.github/workflows/deploy-aws.yml +130 -0
  10. package/.github/workflows/lint-and-format.yml +33 -0
  11. package/.prettierignore +65 -0
  12. package/.prettierrc +9 -0
  13. package/CHANGELOG.md +3 -0
  14. package/CONTRIBUTING.md +126 -0
  15. package/Dockerfile +27 -0
  16. package/GITHUB_OAUTH_SETUP.md +49 -0
  17. package/GOOGLE_OAUTH_SETUP.md +148 -0
  18. package/LICENSE +201 -0
  19. package/README.md +134 -0
  20. package/assets/Dark.svg +23 -0
  21. package/assets/archDiagram.png +0 -0
  22. package/assets/banner.png +0 -0
  23. package/assets/mcpInstallv2.png +0 -0
  24. package/assets/sampleResponse.png +0 -0
  25. package/assets/signin.png +0 -0
  26. package/assets/userflow.png +0 -0
  27. package/backend/migrations/000_create-base-tables.sql +142 -0
  28. package/backend/migrations/001_create-helper-functions.sql +41 -0
  29. package/backend/migrations/002_rename-auth-tables.sql +30 -0
  30. package/backend/migrations/003_create-users-table.sql +56 -0
  31. package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
  32. package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
  33. package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
  34. package/backend/migrations/007_drop-metadata-table.sql +2 -0
  35. package/backend/migrations/008_add-system-tables.sql +77 -0
  36. package/backend/migrations/009_add-function-secrets.sql +24 -0
  37. package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
  38. package/backend/migrations/011_refactor-secrets-table.sql +15 -0
  39. package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
  40. package/backend/package.json +75 -0
  41. package/backend/src/api/middleware/auth.ts +240 -0
  42. package/backend/src/api/middleware/error.ts +231 -0
  43. package/backend/src/api/middleware/upload.ts +59 -0
  44. package/backend/src/api/routes/agent.ts +29 -0
  45. package/backend/src/api/routes/ai.ts +472 -0
  46. package/backend/src/api/routes/auth.oauth.ts +482 -0
  47. package/backend/src/api/routes/auth.ts +386 -0
  48. package/backend/src/api/routes/database.advance.ts +275 -0
  49. package/backend/src/api/routes/database.records.ts +246 -0
  50. package/backend/src/api/routes/database.tables.ts +161 -0
  51. package/backend/src/api/routes/docs.ts +66 -0
  52. package/backend/src/api/routes/functions.ts +183 -0
  53. package/backend/src/api/routes/logs.ts +150 -0
  54. package/backend/src/api/routes/metadata.ts +160 -0
  55. package/backend/src/api/routes/openapi.ts +82 -0
  56. package/backend/src/api/routes/secrets.ts +199 -0
  57. package/backend/src/api/routes/storage.ts +547 -0
  58. package/backend/src/api/routes/usage.ts +96 -0
  59. package/backend/src/core/ai/chat.ts +207 -0
  60. package/backend/src/core/ai/client.ts +242 -0
  61. package/backend/src/core/ai/config.ts +187 -0
  62. package/backend/src/core/ai/image.ts +156 -0
  63. package/backend/src/core/ai/model.ts +117 -0
  64. package/backend/src/core/ai/usage.ts +290 -0
  65. package/backend/src/core/auth/auth.ts +781 -0
  66. package/backend/src/core/auth/oauth.ts +398 -0
  67. package/backend/src/core/database/advance.ts +1074 -0
  68. package/backend/src/core/database/manager.ts +178 -0
  69. package/backend/src/core/database/table.ts +772 -0
  70. package/backend/src/core/documentation/agent.ts +689 -0
  71. package/backend/src/core/documentation/openapi.ts +856 -0
  72. package/backend/src/core/functions/functions.ts +310 -0
  73. package/backend/src/core/logs/analytics.ts +76 -0
  74. package/backend/src/core/logs/audit.ts +255 -0
  75. package/backend/src/core/logs/providers/base.provider.ts +83 -0
  76. package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
  77. package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
  78. package/backend/src/core/secrets/encryption.ts +58 -0
  79. package/backend/src/core/secrets/secrets.ts +410 -0
  80. package/backend/src/core/socket/socket.ts +388 -0
  81. package/backend/src/core/socket/types.ts +79 -0
  82. package/backend/src/core/storage/storage.ts +923 -0
  83. package/backend/src/server.ts +288 -0
  84. package/backend/src/types/ai.ts +46 -0
  85. package/backend/src/types/auth.ts +90 -0
  86. package/backend/src/types/database.ts +136 -0
  87. package/backend/src/types/error-constants.ts +86 -0
  88. package/backend/src/types/logs.ts +47 -0
  89. package/backend/src/types/profile.ts +55 -0
  90. package/backend/src/types/storage.ts +23 -0
  91. package/backend/src/utils/cloud-token.ts +39 -0
  92. package/backend/src/utils/constants.ts +1 -0
  93. package/backend/src/utils/environment.ts +35 -0
  94. package/backend/src/utils/helpers.ts +49 -0
  95. package/backend/src/utils/logger.ts +13 -0
  96. package/backend/src/utils/response.ts +62 -0
  97. package/backend/src/utils/seed.ts +205 -0
  98. package/backend/src/utils/sql-parser.ts +63 -0
  99. package/backend/src/utils/uuid.ts +9 -0
  100. package/backend/src/utils/validations.ts +129 -0
  101. package/backend/tests/README.md +134 -0
  102. package/backend/tests/cleanup-all-test-data.sh +231 -0
  103. package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
  104. package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
  105. package/backend/tests/local/test-auth-router.sh +144 -0
  106. package/backend/tests/local/test-database-router.sh +222 -0
  107. package/backend/tests/local/test-e2e.sh +241 -0
  108. package/backend/tests/local/test-fk-errors.sh +97 -0
  109. package/backend/tests/local/test-id-field.sh +201 -0
  110. package/backend/tests/local/test-public-bucket.sh +265 -0
  111. package/backend/tests/local/test-secrets.sh +248 -0
  112. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
  113. package/backend/tests/local/test-traditional-rest.sh +209 -0
  114. package/backend/tests/manual/README.md +51 -0
  115. package/backend/tests/manual/create-large-table-simple.sql +11 -0
  116. package/backend/tests/manual/seed-large-table.sql +101 -0
  117. package/backend/tests/manual/setup-large-table-extras.sql +34 -0
  118. package/backend/tests/manual/test-better-auth.sh +303 -0
  119. package/backend/tests/manual/test-bulk-upsert.sh +410 -0
  120. package/backend/tests/manual/test-database-advance.sh +297 -0
  121. package/backend/tests/manual/test-postgrest-stability.sh +192 -0
  122. package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
  123. package/backend/tests/manual/test-universal-storage.sh +264 -0
  124. package/backend/tests/manual/test-users.sql +18 -0
  125. package/backend/tests/run-all-tests.sh +140 -0
  126. package/backend/tests/setup.ts +22 -0
  127. package/backend/tests/test-config.sh +303 -0
  128. package/backend/tsconfig.json +23 -0
  129. package/backend/tsup.config.ts +18 -0
  130. package/backend/vitest.config.ts +22 -0
  131. package/docker-compose.prod.yml +145 -0
  132. package/docker-compose.yml +167 -0
  133. package/docker-init/db/db-init.sql +125 -0
  134. package/docker-init/db/jwt.sql +5 -0
  135. package/docker-init/db/logs.sql +9 -0
  136. package/docker-init/db/postgresql.conf +17 -0
  137. package/docs/deprecated/insforge-auth-api.md +215 -0
  138. package/docs/deprecated/insforge-auth-sdk.md +100 -0
  139. package/docs/deprecated/insforge-db-api.md +359 -0
  140. package/docs/deprecated/insforge-db-sdk.md +140 -0
  141. package/docs/deprecated/insforge-debug-sdk.md +157 -0
  142. package/docs/deprecated/insforge-debug.md +65 -0
  143. package/docs/deprecated/insforge-instructions.md +124 -0
  144. package/docs/deprecated/insforge-project.md +118 -0
  145. package/docs/deprecated/insforge-storage-api.md +279 -0
  146. package/docs/deprecated/insforge-storage-sdk.md +159 -0
  147. package/docs/insforge-instructions-sdk.md +407 -0
  148. package/eslint.config.js +317 -0
  149. package/examples/oauth/frontend-oauth-example.html +251 -0
  150. package/examples/response-examples.md +444 -0
  151. package/frontend/README.md +112 -0
  152. package/frontend/components.json +17 -0
  153. package/frontend/index.html +13 -0
  154. package/frontend/package.json +63 -0
  155. package/frontend/public/favicon.ico +0 -0
  156. package/frontend/src/App.tsx +106 -0
  157. package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
  158. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
  159. package/frontend/src/assets/icons/checked.svg +3 -0
  160. package/frontend/src/assets/icons/error.svg +3 -0
  161. package/frontend/src/assets/icons/pencil.svg +4 -0
  162. package/frontend/src/assets/icons/refresh.svg +4 -0
  163. package/frontend/src/assets/icons/step_active.svg +3 -0
  164. package/frontend/src/assets/icons/step_inactive.svg +11 -0
  165. package/frontend/src/assets/icons/warning.svg +3 -0
  166. package/frontend/src/assets/logos/amazon.svg +1 -0
  167. package/frontend/src/assets/logos/claude_code.svg +3 -0
  168. package/frontend/src/assets/logos/cline.svg +6 -0
  169. package/frontend/src/assets/logos/cursor.svg +20 -0
  170. package/frontend/src/assets/logos/discord.svg +9 -0
  171. package/frontend/src/assets/logos/gemini.svg +19 -0
  172. package/frontend/src/assets/logos/github.svg +5 -0
  173. package/frontend/src/assets/logos/google.svg +13 -0
  174. package/frontend/src/assets/logos/grok.svg +10 -0
  175. package/frontend/src/assets/logos/insforge_dark.svg +15 -0
  176. package/frontend/src/assets/logos/insforge_light.svg +15 -0
  177. package/frontend/src/assets/logos/openai.svg +10 -0
  178. package/frontend/src/assets/logos/roo_code.svg +9 -0
  179. package/frontend/src/assets/logos/trae.svg +3 -0
  180. package/frontend/src/assets/logos/windsurf.svg +10 -0
  181. package/frontend/src/components/ButtonWithLoading.tsx +27 -0
  182. package/frontend/src/components/Checkbox.tsx +61 -0
  183. package/frontend/src/components/CodeBlock.tsx +32 -0
  184. package/frontend/src/components/ConfirmDialog.tsx +96 -0
  185. package/frontend/src/components/CopyButton.tsx +69 -0
  186. package/frontend/src/components/DeleteActionButton.tsx +42 -0
  187. package/frontend/src/components/EmptyState.tsx +41 -0
  188. package/frontend/src/components/ErrorState.tsx +35 -0
  189. package/frontend/src/components/FeatureSidebar.tsx +126 -0
  190. package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
  191. package/frontend/src/components/JsonHighlight.tsx +61 -0
  192. package/frontend/src/components/LoadingState.tsx +16 -0
  193. package/frontend/src/components/PaginationControls.tsx +54 -0
  194. package/frontend/src/components/PromptDialog.tsx +68 -0
  195. package/frontend/src/components/SearchInput.tsx +90 -0
  196. package/frontend/src/components/SelectionClearButton.tsx +26 -0
  197. package/frontend/src/components/Stepper.tsx +139 -0
  198. package/frontend/src/components/ThemeToggle.tsx +58 -0
  199. package/frontend/src/components/TypeBadge.tsx +20 -0
  200. package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
  201. package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
  202. package/frontend/src/components/datagrid/IdCell.tsx +44 -0
  203. package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
  204. package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
  205. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
  206. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
  207. package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
  208. package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
  209. package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
  210. package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
  211. package/frontend/src/components/datagrid/index.tsx +20 -0
  212. package/frontend/src/components/index.ts +39 -0
  213. package/frontend/src/components/layout/AppHeader.tsx +146 -0
  214. package/frontend/src/components/layout/AppSidebar.tsx +190 -0
  215. package/frontend/src/components/layout/CloudLayout.tsx +95 -0
  216. package/frontend/src/components/layout/Layout.tsx +43 -0
  217. package/frontend/src/components/radix/Alert.tsx +45 -0
  218. package/frontend/src/components/radix/AlertDialog.tsx +115 -0
  219. package/frontend/src/components/radix/Avatar.tsx +45 -0
  220. package/frontend/src/components/radix/Badge.tsx +33 -0
  221. package/frontend/src/components/radix/Button.tsx +50 -0
  222. package/frontend/src/components/radix/Card.tsx +58 -0
  223. package/frontend/src/components/radix/Dialog.tsx +98 -0
  224. package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
  225. package/frontend/src/components/radix/Form.tsx +167 -0
  226. package/frontend/src/components/radix/Input.tsx +22 -0
  227. package/frontend/src/components/radix/Label.tsx +19 -0
  228. package/frontend/src/components/radix/Popover.tsx +29 -0
  229. package/frontend/src/components/radix/ScrollArea.tsx +44 -0
  230. package/frontend/src/components/radix/Select.tsx +151 -0
  231. package/frontend/src/components/radix/Separator.tsx +26 -0
  232. package/frontend/src/components/radix/Sheet.tsx +119 -0
  233. package/frontend/src/components/radix/Skeleton.tsx +7 -0
  234. package/frontend/src/components/radix/Switch.tsx +29 -0
  235. package/frontend/src/components/radix/Tabs.tsx +50 -0
  236. package/frontend/src/components/radix/Textarea.tsx +21 -0
  237. package/frontend/src/components/radix/Tooltip.tsx +28 -0
  238. package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
  239. package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
  240. package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
  241. package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
  242. package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
  243. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
  244. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
  245. package/frontend/src/features/ai/helpers.ts +155 -0
  246. package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
  247. package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
  248. package/frontend/src/features/ai/page/AIPage.tsx +178 -0
  249. package/frontend/src/features/ai/services/ai.service.ts +148 -0
  250. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
  251. package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
  252. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
  253. package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
  254. package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
  255. package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
  256. package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
  257. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
  258. package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
  259. package/frontend/src/features/auth/index.ts +9 -0
  260. package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
  261. package/frontend/src/features/auth/services/auth.service.ts +112 -0
  262. package/frontend/src/features/auth/services/oauth.service.ts +49 -0
  263. package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
  264. package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
  265. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
  266. package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
  267. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
  268. package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
  269. package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
  270. package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
  271. package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
  272. package/frontend/src/features/database/components/TableForm.tsx +656 -0
  273. package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
  274. package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
  275. package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
  276. package/frontend/src/features/database/constants.ts +26 -0
  277. package/frontend/src/features/database/helpers.ts +125 -0
  278. package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
  279. package/frontend/src/features/database/index.ts +12 -0
  280. package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
  281. package/frontend/src/features/database/schema.ts +25 -0
  282. package/frontend/src/features/database/services/database.service.ts +216 -0
  283. package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
  284. package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
  285. package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
  286. package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
  287. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
  288. package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
  289. package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
  290. package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
  291. package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
  292. package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
  293. package/frontend/src/features/functions/services/functions.service.ts +48 -0
  294. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
  295. package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
  296. package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
  297. package/frontend/src/features/login/page/LoginPage.tsx +174 -0
  298. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
  299. package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
  300. package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
  301. package/frontend/src/features/logs/index.ts +5 -0
  302. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
  303. package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
  304. package/frontend/src/features/logs/services/log.service.ts +171 -0
  305. package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
  306. package/frontend/src/features/metadata/index.ts +0 -0
  307. package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
  308. package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
  309. package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
  310. package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
  311. package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
  312. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
  313. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
  314. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
  315. package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
  316. package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
  317. package/frontend/src/features/onboard/index.ts +3 -0
  318. package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
  319. package/frontend/src/features/onboard/types.ts +8 -0
  320. package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
  321. package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
  322. package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
  323. package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
  324. package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
  325. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
  326. package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
  327. package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
  328. package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
  329. package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
  330. package/frontend/src/features/storage/index.ts +3 -0
  331. package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
  332. package/frontend/src/features/storage/services/storage.service.ts +144 -0
  333. package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
  334. package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
  335. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
  336. package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
  337. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
  338. package/frontend/src/features/visualizer/components/index.ts +5 -0
  339. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
  340. package/frontend/src/index.css +248 -0
  341. package/frontend/src/lib/api/client.ts +163 -0
  342. package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
  343. package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
  344. package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
  345. package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
  346. package/frontend/src/lib/hooks/useAuth.ts +4 -0
  347. package/frontend/src/lib/hooks/useConfirm.ts +55 -0
  348. package/frontend/src/lib/hooks/useInterval.ts +27 -0
  349. package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
  350. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
  351. package/frontend/src/lib/hooks/usePagination.ts +27 -0
  352. package/frontend/src/lib/hooks/useTimeout.ts +27 -0
  353. package/frontend/src/lib/hooks/useToast.tsx +229 -0
  354. package/frontend/src/lib/utils/constants.ts +38 -0
  355. package/frontend/src/lib/utils/utils.ts +165 -0
  356. package/frontend/src/lib/utils/validation-schemas.ts +126 -0
  357. package/frontend/src/main.tsx +16 -0
  358. package/frontend/src/rdg.css +194 -0
  359. package/frontend/src/vite-env.d.ts +12 -0
  360. package/frontend/tailwind.config.js +97 -0
  361. package/frontend/tsconfig.json +26 -0
  362. package/frontend/tsconfig.node.json +10 -0
  363. package/frontend/vite.config.ts +37 -0
  364. package/frontend/vitest.config.ts +36 -0
  365. package/functions/deno.json +25 -0
  366. package/functions/server.ts +290 -0
  367. package/functions/worker-template.js +126 -0
  368. package/openapi/ai.yaml +689 -0
  369. package/openapi/auth.yaml +563 -0
  370. package/openapi/functions.yaml +476 -0
  371. package/openapi/health.yaml +30 -0
  372. package/openapi/logs.yaml +224 -0
  373. package/openapi/metadata.yaml +178 -0
  374. package/openapi/records.yaml +382 -0
  375. package/openapi/secrets.yaml +371 -0
  376. package/openapi/storage.yaml +876 -0
  377. package/openapi/tables.yaml +464 -0
  378. package/package.json +88 -0
  379. package/shared-schemas/package.json +31 -0
  380. package/shared-schemas/src/ai-api.schema.ts +167 -0
  381. package/shared-schemas/src/ai.schema.ts +54 -0
  382. package/shared-schemas/src/auth-api.schema.ts +193 -0
  383. package/shared-schemas/src/auth.schema.ts +94 -0
  384. package/shared-schemas/src/database-api.schema.ts +259 -0
  385. package/shared-schemas/src/database.schema.ts +69 -0
  386. package/shared-schemas/src/functions-api.schema.ts +25 -0
  387. package/shared-schemas/src/functions.schema.ts +16 -0
  388. package/shared-schemas/src/index.ts +13 -0
  389. package/shared-schemas/src/logs-api.schema.ts +49 -0
  390. package/shared-schemas/src/logs.schema.ts +14 -0
  391. package/shared-schemas/src/metadata.schema.ts +56 -0
  392. package/shared-schemas/src/storage-api.schema.ts +65 -0
  393. package/shared-schemas/src/storage.schema.ts +19 -0
  394. package/shared-schemas/tsconfig.json +21 -0
  395. package/tsconfig.json +8 -0
@@ -0,0 +1,216 @@
1
+ import { ConvertedValue } from '@/components/datagrid/datagridTypes';
2
+ import { apiClient } from '@/lib/api/client';
3
+ import {
4
+ ColumnSchema,
5
+ GetTableSchemaResponse,
6
+ CreateTableRequest,
7
+ UpdateTableSchemaResponse,
8
+ UpdateTableSchemaRequest,
9
+ } from '@insforge/shared-schemas';
10
+
11
+ export class DatabaseService {
12
+ // Table operations
13
+ async listTables(): Promise<string[]> {
14
+ const data = await apiClient.request('/database/tables', {
15
+ headers: apiClient.withAccessToken(),
16
+ });
17
+ // data is already unwrapped by request method and should be an array
18
+ return Array.isArray(data) ? data : [];
19
+ }
20
+
21
+ getTableSchema(tableName: string): Promise<GetTableSchemaResponse> {
22
+ return apiClient.request(`/database/tables/${tableName}/schema`, {
23
+ headers: apiClient.withAccessToken(),
24
+ });
25
+ }
26
+
27
+ createTable(tableName: string, columns: ColumnSchema[]) {
28
+ const body: CreateTableRequest = { tableName: tableName, columns, rlsEnabled: true };
29
+ return apiClient.request('/database/tables', {
30
+ method: 'POST',
31
+ headers: apiClient.withAccessToken({
32
+ 'Content-Type': 'application/json',
33
+ }),
34
+ body: JSON.stringify(body),
35
+ });
36
+ }
37
+
38
+ deleteTable(tableName: string) {
39
+ return apiClient.request(`/database/tables/${tableName}`, {
40
+ method: 'DELETE',
41
+ headers: apiClient.withAccessToken(),
42
+ });
43
+ }
44
+
45
+ updateTableSchema(
46
+ tableName: string,
47
+ operations: UpdateTableSchemaRequest
48
+ ): Promise<UpdateTableSchemaResponse | void> {
49
+ return apiClient.request(`/database/tables/${tableName}/schema`, {
50
+ method: 'PATCH',
51
+ headers: apiClient.withAccessToken({
52
+ 'Content-Type': 'application/json',
53
+ }),
54
+ body: JSON.stringify(operations),
55
+ });
56
+ }
57
+
58
+ // Record operations
59
+ /**
60
+ * Data fetching method with built-in search, sorting, and pagination for UI components.
61
+ *
62
+ * @param tableName - Name of the table
63
+ * @param limit - Number of records to fetch
64
+ * @param offset - Number of records to skip
65
+ * @param searchQuery - Search term to filter text columns
66
+ * @param sortColumns - Sorting configuration
67
+ * @returns Structured response with records and pagination info
68
+ */
69
+ async getTableRecords(
70
+ tableName: string,
71
+ limit = 10,
72
+ offset = 0,
73
+ searchQuery?: string,
74
+ sortColumns?: { columnKey: string; direction: string }[]
75
+ ) {
76
+ const params = new URLSearchParams();
77
+ params.set('limit', limit.toString());
78
+ params.set('offset', offset.toString());
79
+
80
+ // Construct PostgREST filter directly in frontend if search query is provided
81
+ if (searchQuery && searchQuery.trim()) {
82
+ const searchValue = searchQuery.trim();
83
+
84
+ // Get table schema to identify text columns
85
+ const schema = await this.getTableSchema(tableName);
86
+ const textColumns = schema.columns
87
+ .filter((col: ColumnSchema) => {
88
+ const type = col.type.toLowerCase();
89
+ return type === 'string';
90
+ })
91
+ .map((col: ColumnSchema) => col.columnName);
92
+
93
+ if (textColumns.length > 0) {
94
+ // Create PostgREST OR filter for text columns
95
+ const orFilters = textColumns
96
+ .map((column: string) => `${column}.ilike.*${searchValue}*`)
97
+ .join(',');
98
+ params.set('or', `(${orFilters})`);
99
+ }
100
+ }
101
+
102
+ // Add sorting if provided - PostgREST uses "order" parameter
103
+ if (sortColumns && sortColumns.length > 0) {
104
+ const orderParam = sortColumns
105
+ .map((col) => `${col.columnKey}.${col.direction.toLowerCase()}`)
106
+ .join(',');
107
+ params.set('order', orderParam);
108
+ }
109
+
110
+ const response: {
111
+ data: { [key: string]: ConvertedValue }[];
112
+ pagination: { offset: number; limit: number; total: number };
113
+ } = await apiClient.request(`/database/records/${tableName}?${params.toString()}`, {
114
+ headers: {
115
+ Prefer: 'count=exact',
116
+ },
117
+ });
118
+
119
+ return {
120
+ records: response.data,
121
+ pagination: response.pagination,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Get a single record by foreign key value.
127
+ * Specifically designed for foreign key lookups.
128
+ *
129
+ * @param tableName - Name of the table to search in
130
+ * @param columnName - Name of the column to filter by
131
+ * @param value - Value to match
132
+ * @returns Single record or null if not found
133
+ */
134
+ async getRecordByForeignKeyValue(tableName: string, columnName: string, value: string) {
135
+ const queryParams = `${columnName}=eq.${encodeURIComponent(value)}&limit=1`;
136
+ const response = await this.getRecords(tableName, queryParams);
137
+
138
+ // Return the first record if found, or null if not found
139
+ if (response.records && response.records.length > 0) {
140
+ return response.records[0];
141
+ }
142
+ return null;
143
+ }
144
+
145
+ async getRecords(tableName: string, queryParams: string = '') {
146
+ const url = `/database/records/${tableName}${queryParams ? `?${queryParams}` : ''}`;
147
+ const response = await apiClient.request(url, {
148
+ headers: apiClient.withAccessToken(),
149
+ });
150
+
151
+ // Traditional REST: check if response is array (direct data) or wrapped
152
+ if (Array.isArray(response)) {
153
+ return {
154
+ records: response,
155
+ total: response.length,
156
+ };
157
+ }
158
+
159
+ // If backend returns wrapped format for this endpoint
160
+ if (response.data && Array.isArray(response.data)) {
161
+ return {
162
+ records: response.data,
163
+ total: response.data.length,
164
+ };
165
+ }
166
+
167
+ return {
168
+ records: response,
169
+ total: response.length,
170
+ };
171
+ }
172
+
173
+ createRecords(table: string, records: { [key: string]: ConvertedValue }[]) {
174
+ // if data is json and data[id] == "" then remove id from data, because can't assign '' to uuid
175
+ records = records.map((record) => {
176
+ if (typeof record === 'object' && record.id === '') {
177
+ delete record.id;
178
+ }
179
+ return record;
180
+ });
181
+ return apiClient.request(`/database/records/${table}`, {
182
+ method: 'POST',
183
+ headers: apiClient.withAccessToken({
184
+ 'Content-Type': 'application/json',
185
+ }),
186
+ body: JSON.stringify(records),
187
+ });
188
+ }
189
+
190
+ createRecord(table: string, data: { [key: string]: ConvertedValue }) {
191
+ if (typeof data === 'object' && data.id === '') {
192
+ // can't assign '' to uuid, so we need to remove it
193
+ delete data.id;
194
+ }
195
+ return this.createRecords(table, [data]);
196
+ }
197
+
198
+ updateRecord(table: string, id: string, data: { [key: string]: ConvertedValue }) {
199
+ return apiClient.request(`/database/records/${table}?id=eq.${id}`, {
200
+ method: 'PATCH',
201
+ headers: apiClient.withAccessToken({
202
+ 'Content-Type': 'application/json',
203
+ }),
204
+ body: JSON.stringify(data),
205
+ });
206
+ }
207
+
208
+ deleteRecord(table: string, id: string) {
209
+ return apiClient.request(`/database/records/${table}?id=eq.${id}`, {
210
+ method: 'DELETE',
211
+ headers: apiClient.withAccessToken(),
212
+ });
213
+ }
214
+ }
215
+
216
+ export const databaseService = new DatabaseService();
@@ -0,0 +1,15 @@
1
+ import { Code2 } from 'lucide-react';
2
+
3
+ export default function FunctionEmptyState() {
4
+ return (
5
+ <div className="flex flex-col items-center justify-center py-8 text-center gap-3 rounded-[8px] bg-neutral-100 dark:bg-[#333333]">
6
+ <Code2 size={40} className="text-neutral-400 dark:text-neutral-600" />
7
+ <div className="flex flex-col items-center justify-center gap-1">
8
+ <p className="text-sm font-medium text-zinc-950 dark:text-white">No functions available</p>
9
+ <p className="text-neutral-500 dark:text-neutral-400 text-xs">
10
+ No edge functions have been created yet
11
+ </p>
12
+ </div>
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,71 @@
1
+ import { CopyButton } from '@/components/CopyButton';
2
+ import { type EdgeFunction } from '../services/functions.service';
3
+ import { cn, getBackendUrl } from '@/lib/utils/utils';
4
+ import { format, formatDistance } from 'date-fns';
5
+ interface FunctionRowProps {
6
+ function: EdgeFunction;
7
+ onClick: () => void;
8
+ className?: string;
9
+ }
10
+
11
+ export function FunctionRow({ function: func, onClick, className }: FunctionRowProps) {
12
+ const functionUrl = `${getBackendUrl()}/functions/${func.slug}`;
13
+
14
+ return (
15
+ <div
16
+ className={cn(
17
+ 'group h-14 px-3 bg-white hover:bg-neutral-100 dark:bg-[#333333] dark:hover:bg-neutral-700 rounded-[8px] transition-all cursor-pointer',
18
+ className
19
+ )}
20
+ onClick={onClick}
21
+ >
22
+ <div className="grid grid-cols-12 h-full items-center">
23
+ {/* Name Column */}
24
+ <div className="col-span-2 min-w-0 px-3 py-1.5">
25
+ <p className="text-sm text-zinc-950 dark:text-white truncate" title={func.name}>
26
+ {func.name}
27
+ </p>
28
+ </div>
29
+
30
+ {/* URL Column */}
31
+ <div className="col-span-6 min-w-0 px-3 py-1.5">
32
+ <div className="flex items-center gap-3">
33
+ <span
34
+ className="text-sm text-muted-foreground dark:text-white truncate"
35
+ title={functionUrl}
36
+ >
37
+ {functionUrl}
38
+ </span>
39
+ <CopyButton
40
+ showText={false}
41
+ text={functionUrl}
42
+ className="h-7 w-7 dark:hover:bg-neutral-500 dark:data-[copied=true]:group-hover:bg-neutral-700 dark:data-[copied=true]:hover:bg-neutral-700"
43
+ />
44
+ </div>
45
+ </div>
46
+
47
+ {/* Created Column */}
48
+ <div className="col-span-2 px-3 py-1.5">
49
+ <span
50
+ className="text-sm text-muted-foreground dark:text-white truncate"
51
+ title={func.created_at}
52
+ >
53
+ {format(new Date(func.created_at), 'MMM dd, yyyy HH:mm')}
54
+ </span>
55
+ </div>
56
+
57
+ {/* Last Update Column */}
58
+ <div className="col-span-2 px-3 py-1.5">
59
+ <span
60
+ className="text-sm text-muted-foreground dark:text-white truncate"
61
+ title={func.deployed_at}
62
+ >
63
+ {func.deployed_at
64
+ ? formatDistance(new Date(func.deployed_at), new Date(), { addSuffix: true })
65
+ : 'Never'}
66
+ </span>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,46 @@
1
+ interface CodeEditorProps {
2
+ code: string;
3
+ }
4
+
5
+ export function CodeEditor({ code }: CodeEditorProps) {
6
+ // Split code into lines for line numbers
7
+ const lines = code.split('\n');
8
+
9
+ return (
10
+ <div className="h-full bg-white dark:bg-neutral-900 overflow-auto">
11
+ <div className="flex min-h-full">
12
+ {/* Line Numbers */}
13
+ <div className="flex-shrink-0 bg-gray-50 dark:bg-neutral-800 px-3 py-4 font-mono text-sm text-gray-500 dark:text-gray-400 select-none">
14
+ {lines.map((_, index) => (
15
+ <div key={index} className="leading-6 text-right min-w-[2rem]">
16
+ {index + 1}
17
+ </div>
18
+ ))}
19
+ {/* Extra line numbers for blank lines */}
20
+ {Array.from({ length: 6 }, (_, i) => (
21
+ <div key={`extra-${i}`} className="leading-6 text-right min-w-[2rem]">
22
+ {lines.length + 1 + i}
23
+ </div>
24
+ ))}
25
+ </div>
26
+
27
+ {/* Code Area */}
28
+ <div className="flex-1">
29
+ <pre className="font-mono text-sm leading-6 p-4 m-0 bg-transparent text-gray-900 dark:text-white">
30
+ {lines.map((line, index) => (
31
+ <div key={index} className="min-h-[1.5rem]">
32
+ {line || <span>&nbsp;</span>}
33
+ </div>
34
+ ))}
35
+ {/* Some blank lines at the end */}
36
+ {Array.from({ length: 6 }, (_, i) => (
37
+ <div key={`blank-${i}`} className="min-h-[1.5rem]">
38
+ &nbsp;
39
+ </div>
40
+ ))}
41
+ </pre>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ );
46
+ }
@@ -0,0 +1,88 @@
1
+ import { ChevronRight } from 'lucide-react';
2
+ import { Skeleton } from '@/components/radix/Skeleton';
3
+ import { FunctionRow } from './FunctionRow';
4
+ import { CodeEditor } from './FunctionViewer';
5
+ import FunctionEmptyState from './FunctionEmptyState';
6
+ import { useFunctions } from '../hooks/useFunctions';
7
+ import { useToast } from '@/lib/hooks/useToast';
8
+ import { useEffect, useRef } from 'react';
9
+
10
+ export function FunctionsContent() {
11
+ const toastShownRef = useRef(false);
12
+ const { showToast } = useToast();
13
+ const {
14
+ functions,
15
+ isRuntimeAvailable,
16
+ selectedFunction,
17
+ isLoading: loading,
18
+ selectFunction,
19
+ clearSelection,
20
+ } = useFunctions();
21
+
22
+ useEffect(() => {
23
+ if (!isRuntimeAvailable && !toastShownRef.current) {
24
+ toastShownRef.current = true;
25
+ showToast('Function container is unhealthy.', 'error');
26
+ }
27
+ }, [isRuntimeAvailable, showToast]);
28
+
29
+ // If a function is selected, show the detail view
30
+ if (selectedFunction) {
31
+ return (
32
+ <div className="flex flex-col h-full">
33
+ <div className="flex items-center gap-2.5 p-4 border-b border-border-gray dark:border-neutral-600">
34
+ <button
35
+ onClick={clearSelection}
36
+ className="text-xl text-zinc-500 dark:text-neutral-400 hover:text-zinc-950 dark:hover:text-white transition-colors"
37
+ >
38
+ Functions
39
+ </button>
40
+ <ChevronRight className="w-5 h-5 text-muted-foreground dark:text-neutral-400" />
41
+ <p className="text-xl text-zinc-950 dark:text-white">{selectedFunction.name}</p>
42
+ </div>
43
+
44
+ <div className="flex-1 min-h-0">
45
+ <CodeEditor code={selectedFunction.code || '// No code available'} />
46
+ </div>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ // Default list view
52
+ return (
53
+ <div className="flex flex-col gap-6 p-4">
54
+ <p className="h-7 text-xl text-zinc-950 dark:text-white">Functions</p>
55
+ <div className="flex flex-col gap-2">
56
+ {/* Table Header */}
57
+ <div className="grid grid-cols-12 px-3 text-sm text-muted-foreground dark:text-neutral-400">
58
+ <div className="col-span-2 py-1 px-3">Name</div>
59
+ <div className="col-span-6 py-1 px-3">URL</div>
60
+ <div className="col-span-2 py-1 px-3">Created</div>
61
+ <div className="col-span-2 py-1 px-3">Last Update</div>
62
+ </div>
63
+ {loading ? (
64
+ <>
65
+ {[...Array(4)].map((_, i) => (
66
+ <Skeleton key={i} className="h-14 rounded-[8px] cols-span-full" />
67
+ ))}
68
+ </>
69
+ ) : functions.length >= 1 ? (
70
+ <>
71
+ {functions.map((func) => (
72
+ <FunctionRow
73
+ key={func.id}
74
+ function={func}
75
+ onClick={() => void selectFunction(func)}
76
+ className="cols-span-full"
77
+ />
78
+ ))}
79
+ </>
80
+ ) : (
81
+ <div className="cols-span-full">
82
+ <FunctionEmptyState />
83
+ </div>
84
+ )}
85
+ </div>
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,56 @@
1
+ import { cn } from '@/lib/utils/utils';
2
+ import { useFunctions } from '../hooks/useFunctions';
3
+
4
+ interface FunctionsSidebarProps {
5
+ selectedSection: 'functions' | 'secrets';
6
+ onSectionSelect: (section: 'functions' | 'secrets') => void;
7
+ }
8
+
9
+ export function FunctionsSidebar({ selectedSection, onSectionSelect }: FunctionsSidebarProps) {
10
+ const { clearSelection } = useFunctions();
11
+ const sections = [
12
+ {
13
+ id: 'functions' as const,
14
+ name: 'Functions',
15
+ },
16
+ {
17
+ id: 'secrets' as const,
18
+ name: 'Secrets',
19
+ },
20
+ ];
21
+
22
+ return (
23
+ <div className="w-60 px-3 py-4 flex flex-col h-full bg-white dark:bg-neutral-800 border-r border-border-gray dark:border-neutral-700">
24
+ <div className="mb-4 w-full">
25
+ <p className="text-base text-zinc-950 dark:text-neutral-400">Edge Functions</p>
26
+ </div>
27
+ <div className="flex-1 overflow-y-auto">
28
+ <div className="space-y-2">
29
+ {sections.map((section) => {
30
+ const isSelected = selectedSection === section.id;
31
+
32
+ return (
33
+ <button
34
+ key={section.id}
35
+ onClick={() => {
36
+ if (section.id === 'functions') {
37
+ clearSelection();
38
+ }
39
+ onSectionSelect(section.id);
40
+ }}
41
+ className={cn(
42
+ 'h-9 w-full flex items-center justify-between pl-3 py-1 pr-1 rounded text-left transition-colors',
43
+ isSelected
44
+ ? 'bg-zinc-100 dark:bg-neutral-700 text-zinc-950 dark:text-white'
45
+ : 'hover:bg-zinc-50 dark:hover:bg-neutral-700/50 text-zinc-700 dark:text-zinc-300'
46
+ )}
47
+ >
48
+ <p className="text-sm text-zinc-950 dark:text-white">{section.name}</p>
49
+ </button>
50
+ );
51
+ })}
52
+ </div>
53
+ </div>
54
+ </div>
55
+ );
56
+ }
@@ -0,0 +1,23 @@
1
+ import { Key } from 'lucide-react';
2
+
3
+ interface SecretEmptyStateProps {
4
+ searchQuery: string;
5
+ }
6
+
7
+ export default function SecretEmptyState({ searchQuery }: SecretEmptyStateProps) {
8
+ return (
9
+ <div className="flex flex-col items-center justify-center py-8 rounded-[8px] bg-neutral-100 dark:bg-[#333333]">
10
+ <Key className="w-12 h-12 mx-auto mb-4 text-muted-foreground" />
11
+ <div className="flex flex-col items-center justify-center gap-1">
12
+ <p className="text-sm font-medium text-zinc-950 dark:text-white">
13
+ {searchQuery ? 'No matching secrets found' : 'No secrets configured'}
14
+ </p>
15
+ <p className="text-neutral-500 dark:text-neutral-400 text-sm">
16
+ {searchQuery
17
+ ? 'Try adjusting your search terms'
18
+ : 'Create environment variables for your edge functions'}
19
+ </p>
20
+ </div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,68 @@
1
+ import { Trash2 } from 'lucide-react';
2
+ import { Button } from '@/components/radix/Button';
3
+ import { type Secret } from '@/features/secrets/services/secrets.service';
4
+ import { cn } from '@/lib/utils/utils';
5
+ import { formatDistance } from 'date-fns';
6
+
7
+ interface SecretRowProps {
8
+ secret: Secret;
9
+ onDelete: (secret: Secret) => void;
10
+ className?: string;
11
+ }
12
+
13
+ export function SecretRow({ secret, onDelete, className }: SecretRowProps) {
14
+ const handleDeleteClick = (e: React.MouseEvent) => {
15
+ e.stopPropagation();
16
+ onDelete(secret);
17
+ };
18
+
19
+ return (
20
+ <div
21
+ className={cn(
22
+ 'group h-14 px-3 bg-white hover:bg-neutral-100 dark:bg-[#333333] dark:hover:bg-neutral-700 rounded-[8px] transition-all',
23
+ className
24
+ )}
25
+ >
26
+ <div className="grid grid-cols-12 h-full items-center">
27
+ {/* Name Column */}
28
+ <div className="col-span-8 min-w-0 px-3 py-1.5">
29
+ <p
30
+ className="text-sm text-zinc-950 dark:text-white truncate font-medium"
31
+ title={secret.key}
32
+ >
33
+ {secret.key}
34
+ </p>
35
+ </div>
36
+
37
+ {/* Digest Column */}
38
+ {/* <div className="col-span-5 min-w-0 px-3 py-1.5">
39
+ <span className="text-sm text-zinc-950 dark:text-white font-mono truncate">
40
+ </span>
41
+ </div> */}
42
+
43
+ {/* Updated at Column */}
44
+ <div className="col-span-3 px-3 py-1.5">
45
+ <span className="text-sm text-zinc-950 dark:text-white truncate">
46
+ {secret.updatedAt
47
+ ? formatDistance(new Date(secret.updatedAt), new Date(), { addSuffix: true })
48
+ : 'Never'}
49
+ </span>
50
+ </div>
51
+
52
+ {/* Delete Button Column */}
53
+ <div className="col-span-1 flex justify-end px-3 py-1.5">
54
+ <Button
55
+ variant="ghost"
56
+ size="sm"
57
+ onClick={handleDeleteClick}
58
+ disabled={secret.isReserved}
59
+ className="h-7 w-7 p-1 text-neutral-500 dark:text-neutral-400 hover:text-black dark:hover:text-white hover:bg-neutral-200 dark:hover:bg-neutral-600"
60
+ title={secret.isReserved ? 'Cannot delete reserved secrets' : 'Delete secret'}
61
+ >
62
+ <Trash2 className="w-5 h-5" />
63
+ </Button>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ );
68
+ }