insforge 1.2.10 → 1.3.0
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.
- package/.claude-plugin/marketplace.json +20 -20
- package/.dockerignore +60 -60
- package/.env.example +83 -77
- package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
- package/.github/ISSUE_TEMPLATE/config.yml +11 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
- package/.github/copilot-instructions.md +146 -146
- package/.github/workflows/build-image.yml +65 -65
- package/.github/workflows/ci-premerge-check.yml +23 -23
- package/.github/workflows/e2e.yml +63 -63
- package/.github/workflows/lint-and-format.yml +32 -32
- package/.prettierignore +64 -64
- package/CHANGELOG.md +44 -44
- package/CLAUDE_PLUGIN.md +104 -104
- package/CODE_OF_CONDUCT.md +128 -128
- package/CONTRIBUTING.md +125 -125
- package/Dockerfile +30 -30
- package/GITHUB_OAUTH_SETUP.md +49 -49
- package/GOOGLE_OAUTH_SETUP.md +148 -148
- package/LICENSE +201 -201
- package/README.md +182 -182
- package/assets/Dark.svg +23 -23
- package/auth/package.json +28 -28
- package/auth/src/lib/broadcastService.ts +117 -115
- package/auth/src/pages/SignInPage.tsx +60 -57
- package/auth/src/pages/SignUpPage.tsx +60 -57
- package/auth/tsconfig.json +32 -32
- package/auth/tsconfig.node.json +11 -11
- package/backend/package.json +78 -75
- package/backend/src/api/routes/ai/index.routes.ts +3 -3
- package/backend/src/api/routes/auth/index.routes.ts +667 -570
- package/backend/src/api/routes/auth/oauth.routes.ts +473 -448
- package/backend/src/api/routes/database/advance.routes.ts +37 -16
- package/backend/src/api/routes/database/index.routes.ts +78 -1
- package/backend/src/api/routes/database/records.routes.ts +10 -10
- package/backend/src/api/routes/database/tables.routes.ts +0 -14
- package/backend/src/api/routes/docs/index.routes.ts +75 -76
- package/backend/src/api/routes/email/index.routes.ts +35 -0
- package/backend/src/api/routes/functions/index.routes.ts +18 -12
- package/backend/src/api/routes/metadata/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
- package/backend/src/api/routes/realtime/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
- package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
- package/backend/src/api/routes/storage/index.routes.ts +18 -12
- package/backend/src/api/routes/usage/index.routes.ts +6 -4
- package/backend/src/infra/database/database.manager.ts +14 -1
- package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
- package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
- package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
- package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
- package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
- package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
- package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
- package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
- package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
- package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
- package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
- package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
- package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
- package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
- package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
- package/backend/src/infra/realtime/realtime.manager.ts +246 -0
- package/backend/src/infra/realtime/webhook-sender.ts +82 -0
- package/backend/src/infra/security/token.manager.ts +219 -125
- package/backend/src/infra/socket/socket.manager.ts +198 -64
- package/backend/src/providers/ai/openrouter.provider.ts +12 -9
- package/backend/src/providers/email/base.provider.ts +4 -7
- package/backend/src/providers/email/cloud.provider.ts +84 -0
- package/backend/src/providers/oauth/apple.provider.ts +266 -0
- package/backend/src/providers/oauth/index.ts +1 -0
- package/backend/src/server.ts +317 -284
- package/backend/src/services/ai/ai-model.service.ts +5 -5
- package/backend/src/services/ai/chat-completion.service.ts +4 -4
- package/backend/src/services/ai/image-generation.service.ts +3 -3
- package/backend/src/services/auth/auth.service.ts +14 -0
- package/backend/src/services/database/database-table.service.ts +0 -9
- package/backend/src/services/database/database.service.ts +127 -0
- package/backend/src/services/email/email.service.ts +5 -7
- package/backend/src/services/realtime/index.ts +3 -0
- package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
- package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
- package/backend/src/services/realtime/realtime-message.service.ts +260 -0
- package/backend/src/types/auth.ts +11 -0
- package/backend/src/types/realtime.ts +18 -0
- package/backend/src/types/socket.ts +7 -31
- package/backend/src/utils/cookies.ts +35 -0
- package/backend/src/utils/s3-config-loader.ts +64 -0
- package/backend/src/utils/seed.ts +301 -298
- package/backend/src/utils/sql-parser.ts +90 -0
- package/backend/tests/README.md +133 -133
- package/backend/tests/cleanup-all-test-data.sh +230 -230
- package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
- package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
- package/backend/tests/local/test-ai-config.sh +129 -129
- package/backend/tests/local/test-ai-usage.sh +80 -80
- package/backend/tests/local/test-auth-router.sh +143 -143
- package/backend/tests/local/test-database-router.sh +222 -222
- package/backend/tests/local/test-e2e.sh +240 -240
- package/backend/tests/local/test-fk-errors.sh +96 -96
- package/backend/tests/local/test-functions.sh +123 -123
- package/backend/tests/local/test-id-field.sh +200 -200
- package/backend/tests/local/test-logs.sh +132 -132
- package/backend/tests/local/test-public-bucket.sh +264 -264
- package/backend/tests/local/test-secrets.sh +249 -249
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
- package/backend/tests/local/test-traditional-rest.sh +208 -208
- package/backend/tests/manual/README.md +50 -50
- package/backend/tests/manual/create-large-table-simple.sql +10 -10
- package/backend/tests/manual/seed-large-table.sql +100 -100
- package/backend/tests/manual/setup-large-table-extras.sql +33 -33
- package/backend/tests/manual/test-bulk-upsert.sh +409 -409
- package/backend/tests/manual/test-database-advance.sh +296 -296
- package/backend/tests/manual/test-postgrest-stability.sh +191 -191
- package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
- package/backend/tests/manual/test-rawsql-modes.sh +244 -244
- package/backend/tests/manual/test-universal-storage.sh +263 -263
- package/backend/tests/manual/test-users.sql +17 -17
- package/backend/tests/run-all-tests.sh +139 -139
- package/backend/tests/setup.ts +0 -0
- package/backend/tests/test-config.sh +338 -338
- package/backend/tests/unit/analyze-query.test.ts +697 -0
- package/backend/tsconfig.json +22 -22
- package/claude-plugin/.claude-plugin/plugin.json +24 -24
- package/claude-plugin/README.md +133 -133
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -270
- package/docker-compose.prod.yml +204 -200
- package/docker-compose.yml +232 -228
- package/docker-init/db/db-init.sql +97 -97
- package/docker-init/db/jwt.sql +5 -5
- package/docker-init/db/postgresql.conf +16 -16
- package/docker-init/logs/vector.yml +236 -236
- package/docs/README.md +44 -44
- package/docs/agent-docs/real-time.md +269 -0
- package/docs/changelog.mdx +119 -67
- package/docs/core-concepts/ai/architecture.mdx +372 -372
- package/docs/core-concepts/ai/sdk.mdx +213 -213
- package/docs/core-concepts/authentication/architecture.mdx +278 -278
- package/docs/core-concepts/authentication/sdk.mdx +414 -414
- package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -529
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -221
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -184
- package/docs/core-concepts/authentication/ui-components/react.mdx +129 -129
- package/docs/core-concepts/database/architecture.mdx +255 -255
- package/docs/core-concepts/database/sdk.mdx +382 -382
- package/docs/core-concepts/email/architecture.mdx +101 -0
- package/docs/core-concepts/email/sdk.mdx +53 -0
- package/docs/core-concepts/functions/architecture.mdx +105 -105
- package/docs/core-concepts/functions/sdk.mdx +184 -184
- package/docs/core-concepts/realtime/architecture.mdx +446 -0
- package/docs/core-concepts/realtime/sdk.mdx +409 -0
- package/docs/core-concepts/storage/architecture.mdx +243 -243
- package/docs/core-concepts/storage/sdk.mdx +253 -253
- package/docs/deployment/README.md +94 -94
- package/docs/deployment/deploy-to-aws-ec2.md +564 -564
- package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
- package/docs/deployment/deploy-to-render.md +441 -441
- package/docs/deprecated/insforge-auth-api.md +214 -214
- package/docs/deprecated/insforge-auth-sdk.md +99 -99
- package/docs/deprecated/insforge-db-api.md +358 -358
- package/docs/deprecated/insforge-db-sdk.md +139 -139
- package/docs/deprecated/insforge-debug-sdk.md +156 -156
- package/docs/deprecated/insforge-debug.md +64 -64
- package/docs/deprecated/insforge-instructions.md +123 -123
- package/docs/deprecated/insforge-project.md +117 -117
- package/docs/deprecated/insforge-storage-api.md +278 -278
- package/docs/deprecated/insforge-storage-sdk.md +158 -158
- package/docs/docs.json +232 -210
- package/docs/examples/framework-guides/nextjs.mdx +131 -131
- package/docs/examples/framework-guides/nuxt.mdx +165 -165
- package/docs/examples/framework-guides/react.mdx +165 -165
- package/docs/examples/framework-guides/svelte.mdx +153 -153
- package/docs/examples/framework-guides/vue.mdx +159 -159
- package/docs/examples/overview.mdx +67 -67
- package/docs/favicon.svg +19 -19
- package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
- package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
- package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
- package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
- package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
- package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
- package/docs/images/icons/ai.svg +4 -4
- package/docs/images/logos/nextjs.svg +4 -4
- package/docs/images/logos/nuxt.svg +4 -4
- package/docs/images/logos/react.svg +5 -5
- package/docs/images/logos/svelte.svg +4 -4
- package/docs/images/logos/vue.svg +5 -5
- package/docs/insforge-instructions-sdk.md +89 -88
- package/docs/introduction.mdx +45 -45
- package/docs/logo/dark.svg +22 -22
- package/docs/logo/light.svg +20 -20
- package/docs/partnership.mdx +651 -646
- package/docs/quickstart.mdx +82 -82
- package/docs/showcase.mdx +52 -52
- package/docs/snippets/sdk-installation.mdx +21 -21
- package/docs/snippets/service-icons.mdx +27 -27
- package/examples/oauth/frontend-oauth-example.html +250 -250
- package/examples/response-examples.md +443 -443
- package/frontend/components.json +17 -17
- package/frontend/package.json +69 -69
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
- package/frontend/src/assets/icons/checked.svg +3 -3
- package/frontend/src/assets/icons/connected.svg +3 -3
- package/frontend/src/assets/icons/error.svg +3 -3
- package/frontend/src/assets/icons/loader.svg +9 -9
- package/frontend/src/assets/icons/pencil.svg +4 -4
- package/frontend/src/assets/icons/refresh.svg +4 -4
- package/frontend/src/assets/icons/step_active.svg +3 -3
- package/frontend/src/assets/icons/step_inactive.svg +11 -11
- package/frontend/src/assets/icons/warning.svg +3 -3
- package/frontend/src/assets/logos/apple.svg +3 -3
- package/frontend/src/assets/logos/claude_code.svg +3 -3
- package/frontend/src/assets/logos/cline.svg +6 -6
- package/frontend/src/assets/logos/cursor.svg +20 -20
- package/frontend/src/assets/logos/discord.svg +8 -8
- package/frontend/src/assets/logos/facebook.svg +3 -3
- package/frontend/src/assets/logos/gemini.svg +19 -19
- package/frontend/src/assets/logos/github.svg +5 -5
- package/frontend/src/assets/logos/google.svg +13 -13
- package/frontend/src/assets/logos/grok.svg +10 -10
- package/frontend/src/assets/logos/insforge_dark.svg +15 -15
- package/frontend/src/assets/logos/insforge_light.svg +15 -15
- package/frontend/src/assets/logos/instagram.svg +1 -1
- package/frontend/src/assets/logos/linkedin.svg +3 -3
- package/frontend/src/assets/logos/openai.svg +10 -10
- package/frontend/src/assets/logos/roo_code.svg +9 -9
- package/frontend/src/assets/logos/spotify.svg +16 -16
- package/frontend/src/assets/logos/tiktok.svg +5 -5
- package/frontend/src/assets/logos/trae.svg +3 -3
- package/frontend/src/assets/logos/windsurf.svg +10 -10
- package/frontend/src/assets/logos/x.svg +3 -3
- package/frontend/src/components/layout/AppHeader.tsx +9 -10
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +6 -0
- package/frontend/src/features/auth/helpers.tsx +8 -0
- package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
- package/frontend/src/features/database/components/SQLModal.tsx +75 -0
- package/frontend/src/features/database/components/TableForm.tsx +0 -4
- package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
- package/frontend/src/features/database/hooks/useTables.ts +32 -28
- package/frontend/src/features/database/index.ts +1 -0
- package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -37
- package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +35 -47
- package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +43 -54
- package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
- package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +35 -47
- package/frontend/src/features/database/services/advance.service.ts +0 -26
- package/frontend/src/features/database/services/database.service.ts +55 -0
- package/frontend/src/features/database/services/table.service.ts +0 -6
- package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
- package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +11 -9
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
- package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
- package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
- package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
- package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
- package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
- package/frontend/src/features/realtime/index.ts +11 -0
- package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
- package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
- package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
- package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
- package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +1 -29
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +3 -3
- package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +1 -35
- package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
- package/frontend/src/lib/routing/AppRoutes.tsx +35 -20
- package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
- package/frontend/src/lib/utils/menuItems.ts +24 -0
- package/frontend/src/lib/utils/utils.ts +14 -1
- package/frontend/tsconfig.json +25 -25
- package/frontend/tsconfig.node.json +9 -9
- package/functions/deno.json +24 -24
- package/functions/server.ts +315 -315
- package/i18n/README.ar.md +130 -130
- package/i18n/README.de.md +130 -130
- package/i18n/README.es.md +154 -154
- package/i18n/README.fr.md +134 -134
- package/i18n/README.hi.md +129 -129
- package/i18n/README.ja.md +174 -174
- package/i18n/README.ko.md +136 -136
- package/i18n/README.pt-BR.md +131 -131
- package/i18n/README.ru.md +129 -129
- package/i18n/README.zh-CN.md +133 -133
- package/openapi/ai.yaml +715 -715
- package/openapi/auth.yaml +1244 -1244
- package/openapi/email.yaml +158 -0
- package/openapi/functions.yaml +475 -475
- package/openapi/health.yaml +29 -29
- package/openapi/logs.yaml +223 -223
- package/openapi/metadata.yaml +177 -177
- package/openapi/realtime.yaml +699 -0
- package/openapi/records.yaml +381 -381
- package/openapi/secrets.yaml +370 -370
- package/openapi/storage.yaml +875 -875
- package/openapi/tables.yaml +463 -463
- package/package.json +97 -97
- package/shared-schemas/package.json +31 -31
- package/shared-schemas/src/ai.schema.ts +63 -59
- package/shared-schemas/src/auth-api.schema.ts +352 -339
- package/shared-schemas/src/auth.schema.ts +1 -1
- package/shared-schemas/src/database-api.schema.ts +32 -1
- package/shared-schemas/src/database.schema.ts +39 -0
- package/shared-schemas/src/docs.schema.ts +26 -0
- package/shared-schemas/src/email-api.schema.ts +30 -0
- package/shared-schemas/src/index.ts +4 -0
- package/shared-schemas/src/metadata.schema.ts +9 -0
- package/shared-schemas/src/realtime-api.schema.ts +111 -0
- package/shared-schemas/src/realtime.schema.ts +143 -0
- package/shared-schemas/tsconfig.json +21 -21
- package/tsconfig.json +7 -7
- package/zeabur/README.md +13 -13
- package/zeabur/template.yml +1032 -1032
- package/.cursor/rules/cursor-rules.mdc +0 -94
- package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
- package/test-gemini.sh +0 -35
- package/test-usage-admin.sh +0 -57
- package/test-usage.sh +0 -50
- /package/frontend/src/features/ai/{page → pages}/AIPage.tsx +0 -0
- /package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +0 -0
- /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
- /package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/MCPLogsPage.tsx +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, useState
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
2
|
import RefreshIcon from '@/assets/icons/refresh.svg?react';
|
|
3
3
|
import {
|
|
4
4
|
Button,
|
|
@@ -13,16 +13,10 @@ import {
|
|
|
13
13
|
TooltipProvider,
|
|
14
14
|
TooltipTrigger,
|
|
15
15
|
} from '@/components';
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
16
|
+
import { useTriggers } from '../hooks/useDatabase';
|
|
17
|
+
import { SQLModal, SQLCellButton } from '../components/SQLModal';
|
|
18
|
+
import type { DatabaseTriggersResponse } from '@insforge/shared-schemas';
|
|
18
19
|
import { isSystemTable } from '../constants';
|
|
19
|
-
import {
|
|
20
|
-
DataUpdatePayload,
|
|
21
|
-
DataUpdateResourceType,
|
|
22
|
-
ServerEvents,
|
|
23
|
-
SocketMessage,
|
|
24
|
-
useSocket,
|
|
25
|
-
} from '@/lib/contexts/SocketContext';
|
|
26
20
|
|
|
27
21
|
interface TriggerRow extends DataGridRowType {
|
|
28
22
|
id: string;
|
|
@@ -34,28 +28,25 @@ interface TriggerRow extends DataGridRowType {
|
|
|
34
28
|
[key: string]: ConvertedValue | { [key: string]: string }[];
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
function
|
|
38
|
-
if (!
|
|
31
|
+
function parseTriggersFromResponse(response: DatabaseTriggersResponse | undefined): TriggerRow[] {
|
|
32
|
+
if (!response?.triggers) {
|
|
39
33
|
return [];
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
const data = metadata.data as ExportDatabaseJsonData;
|
|
43
36
|
const triggers: TriggerRow[] = [];
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
if (isSystemTable(tableName)) {
|
|
38
|
+
response.triggers.forEach((trigger) => {
|
|
39
|
+
if (isSystemTable(trigger.tableName)) {
|
|
47
40
|
return;
|
|
48
41
|
}
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
actionStatement: trigger.actionStatement,
|
|
58
|
-
});
|
|
43
|
+
triggers.push({
|
|
44
|
+
id: `${trigger.tableName}_${trigger.triggerName}`,
|
|
45
|
+
tableName: trigger.tableName,
|
|
46
|
+
triggerName: trigger.triggerName,
|
|
47
|
+
actionTiming: trigger.actionTiming,
|
|
48
|
+
eventManipulation: trigger.eventManipulation,
|
|
49
|
+
actionStatement: trigger.actionStatement,
|
|
59
50
|
});
|
|
60
51
|
});
|
|
61
52
|
|
|
@@ -65,29 +56,10 @@ function parseTriggersFromMetadata(metadata: ExportDatabaseResponse | undefined)
|
|
|
65
56
|
export default function TriggersPage() {
|
|
66
57
|
const [searchQuery, setSearchQuery] = useState('');
|
|
67
58
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
68
|
-
const { data
|
|
69
|
-
|
|
70
|
-
const { socket, isConnected } = useSocket();
|
|
71
|
-
|
|
72
|
-
const allTriggers = useMemo(() => parseTriggersFromMetadata(metadata), [metadata]);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (!socket || !isConnected) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
59
|
+
const { data, isLoading, error, refetch } = useTriggers(true);
|
|
60
|
+
const [sqlModal, setSqlModal] = useState({ open: false, title: '', value: '' });
|
|
78
61
|
|
|
79
|
-
|
|
80
|
-
if (message.payload?.resource === DataUpdateResourceType.DATABASE) {
|
|
81
|
-
void refetch();
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
socket.on(ServerEvents.DATA_UPDATE, handleDataUpdate);
|
|
86
|
-
|
|
87
|
-
return () => {
|
|
88
|
-
socket.off(ServerEvents.DATA_UPDATE, handleDataUpdate);
|
|
89
|
-
};
|
|
90
|
-
}, [socket, isConnected, refetch]);
|
|
62
|
+
const allTriggers = useMemo(() => parseTriggersFromResponse(data), [data]);
|
|
91
63
|
|
|
92
64
|
const filteredTriggers = useMemo(() => {
|
|
93
65
|
if (!searchQuery.trim()) {
|
|
@@ -163,9 +135,17 @@ export default function TriggersPage() {
|
|
|
163
135
|
name: 'Statement',
|
|
164
136
|
width: 'minmax(300px, 3fr)',
|
|
165
137
|
resizable: true,
|
|
138
|
+
renderCell: ({ row }) => (
|
|
139
|
+
<SQLCellButton
|
|
140
|
+
value={row.actionStatement}
|
|
141
|
+
onClick={() =>
|
|
142
|
+
setSqlModal({ open: true, title: 'Trigger Statement', value: row.actionStatement })
|
|
143
|
+
}
|
|
144
|
+
/>
|
|
145
|
+
),
|
|
166
146
|
},
|
|
167
147
|
],
|
|
168
|
-
[]
|
|
148
|
+
[setSqlModal]
|
|
169
149
|
);
|
|
170
150
|
|
|
171
151
|
if (error) {
|
|
@@ -237,6 +217,14 @@ export default function TriggersPage() {
|
|
|
237
217
|
/>
|
|
238
218
|
</div>
|
|
239
219
|
)}
|
|
220
|
+
|
|
221
|
+
{/* SQL Detail Modal */}
|
|
222
|
+
<SQLModal
|
|
223
|
+
open={sqlModal.open}
|
|
224
|
+
onOpenChange={(open) => setSqlModal((prev) => ({ ...prev, open }))}
|
|
225
|
+
title={sqlModal.title}
|
|
226
|
+
value={sqlModal.value}
|
|
227
|
+
/>
|
|
240
228
|
</div>
|
|
241
229
|
);
|
|
242
230
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { apiClient } from '@/lib/api/client';
|
|
2
|
-
import type { ExportDatabaseRequest, ExportDatabaseResponse } from '@insforge/shared-schemas';
|
|
3
2
|
|
|
4
3
|
export interface RawSQLRequest {
|
|
5
4
|
query: string;
|
|
@@ -36,31 +35,6 @@ export class AdvanceService {
|
|
|
36
35
|
body: JSON.stringify(body),
|
|
37
36
|
});
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get full database metadata including schema, functions, triggers, etc.
|
|
42
|
-
* Requires admin privileges.
|
|
43
|
-
*
|
|
44
|
-
* @returns Response with complete database metadata in JSON format
|
|
45
|
-
*/
|
|
46
|
-
async getDatabaseFullMetadata(): Promise<ExportDatabaseResponse> {
|
|
47
|
-
const body: ExportDatabaseRequest = {
|
|
48
|
-
format: 'json',
|
|
49
|
-
includeData: false,
|
|
50
|
-
includeFunctions: true,
|
|
51
|
-
includeSequences: false,
|
|
52
|
-
includeViews: false,
|
|
53
|
-
rowLimit: 1000,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
return apiClient.request('/database/advance/export', {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
headers: apiClient.withAccessToken({
|
|
59
|
-
'Content-Type': 'application/json',
|
|
60
|
-
}),
|
|
61
|
-
body: JSON.stringify(body),
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
38
|
}
|
|
65
39
|
|
|
66
40
|
export const advanceService = new AdvanceService();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { apiClient } from '@/lib/api/client';
|
|
2
|
+
import type {
|
|
3
|
+
DatabaseFunctionsResponse,
|
|
4
|
+
DatabaseIndexesResponse,
|
|
5
|
+
DatabasePoliciesResponse,
|
|
6
|
+
DatabaseTriggersResponse,
|
|
7
|
+
} from '@insforge/shared-schemas';
|
|
8
|
+
|
|
9
|
+
export class DatabaseService {
|
|
10
|
+
/**
|
|
11
|
+
* Get all database functions.
|
|
12
|
+
* Requires admin privileges.
|
|
13
|
+
*/
|
|
14
|
+
async getFunctions(): Promise<DatabaseFunctionsResponse> {
|
|
15
|
+
return apiClient.request('/database/functions', {
|
|
16
|
+
method: 'GET',
|
|
17
|
+
headers: apiClient.withAccessToken({}),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get all database indexes.
|
|
23
|
+
* Requires admin privileges.
|
|
24
|
+
*/
|
|
25
|
+
async getIndexes(): Promise<DatabaseIndexesResponse> {
|
|
26
|
+
return apiClient.request('/database/indexes', {
|
|
27
|
+
method: 'GET',
|
|
28
|
+
headers: apiClient.withAccessToken({}),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get all RLS policies.
|
|
34
|
+
* Requires admin privileges.
|
|
35
|
+
*/
|
|
36
|
+
async getPolicies(): Promise<DatabasePoliciesResponse> {
|
|
37
|
+
return apiClient.request('/database/policies', {
|
|
38
|
+
method: 'GET',
|
|
39
|
+
headers: apiClient.withAccessToken({}),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get all database triggers.
|
|
45
|
+
* Requires admin privileges.
|
|
46
|
+
*/
|
|
47
|
+
async getTriggers(): Promise<DatabaseTriggersResponse> {
|
|
48
|
+
return apiClient.request('/database/triggers', {
|
|
49
|
+
method: 'GET',
|
|
50
|
+
headers: apiClient.withAccessToken({}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const databaseService = new DatabaseService();
|
|
@@ -17,12 +17,6 @@ export class TableService {
|
|
|
17
17
|
return Array.isArray(data) ? data.filter((table) => table !== 'users') : [];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
getAllTableSchemas(): Promise<GetTableSchemaResponse[]> {
|
|
21
|
-
return apiClient.request('/database/tables/schemas', {
|
|
22
|
-
headers: apiClient.withAccessToken(),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
20
|
getTableSchema(tableName: string): Promise<GetTableSchemaResponse> {
|
|
27
21
|
return apiClient.request(`/database/tables/${tableName}/schema`, {
|
|
28
22
|
headers: apiClient.withAccessToken(),
|
|
@@ -14,13 +14,6 @@ import {
|
|
|
14
14
|
TooltipProvider,
|
|
15
15
|
TooltipTrigger,
|
|
16
16
|
} from '@/components';
|
|
17
|
-
import {
|
|
18
|
-
DataUpdatePayload,
|
|
19
|
-
DataUpdateResourceType,
|
|
20
|
-
ServerEvents,
|
|
21
|
-
SocketMessage,
|
|
22
|
-
useSocket,
|
|
23
|
-
} from '@/lib/contexts/SocketContext';
|
|
24
17
|
|
|
25
18
|
export default function FunctionsPage() {
|
|
26
19
|
const toastShownRef = useRef(false);
|
|
@@ -36,8 +29,6 @@ export default function FunctionsPage() {
|
|
|
36
29
|
refetch,
|
|
37
30
|
} = useFunctions();
|
|
38
31
|
|
|
39
|
-
const { socket, isConnected } = useSocket();
|
|
40
|
-
|
|
41
32
|
const handleRefresh = async () => {
|
|
42
33
|
setIsRefreshing(true);
|
|
43
34
|
try {
|
|
@@ -54,24 +45,6 @@ export default function FunctionsPage() {
|
|
|
54
45
|
}
|
|
55
46
|
}, [isRuntimeAvailable, showToast]);
|
|
56
47
|
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (!socket || !isConnected) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const handleDataUpdate = (message: SocketMessage<DataUpdatePayload>) => {
|
|
63
|
-
if (message.payload?.resource === DataUpdateResourceType.FUNCTIONS) {
|
|
64
|
-
void refetch();
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
socket.on(ServerEvents.DATA_UPDATE, handleDataUpdate);
|
|
69
|
-
|
|
70
|
-
return () => {
|
|
71
|
-
socket.off(ServerEvents.DATA_UPDATE, handleDataUpdate);
|
|
72
|
-
};
|
|
73
|
-
}, [socket, isConnected, refetch]);
|
|
74
|
-
|
|
75
48
|
// If a function is selected, show the detail view
|
|
76
49
|
if (selectedFunction) {
|
|
77
50
|
return (
|
|
@@ -124,14 +97,18 @@ export default function FunctionsPage() {
|
|
|
124
97
|
</Tooltip>
|
|
125
98
|
</TooltipProvider>
|
|
126
99
|
</div>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<div className="
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
100
|
+
{/* Table Header */}
|
|
101
|
+
<div className="grid grid-cols-12 px-3 text-sm text-muted-foreground dark:text-neutral-400">
|
|
102
|
+
<div className="col-span-2 py-1 px-3">Name</div>
|
|
103
|
+
<div className="col-span-6 py-1 px-3">URL</div>
|
|
104
|
+
<div className="col-span-2 py-1 px-3">Created</div>
|
|
105
|
+
<div className="col-span-2 py-1 px-3">Last Update</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Scrollable Table Body */}
|
|
110
|
+
<div className="flex-1 min-h-0 overflow-y-auto px-4 pb-4 relative">
|
|
111
|
+
<div className="flex flex-col gap-2">
|
|
135
112
|
{loading ? (
|
|
136
113
|
<>
|
|
137
114
|
{[...Array(4)].map((_, i) => (
|
|
@@ -154,17 +131,17 @@ export default function FunctionsPage() {
|
|
|
154
131
|
<FunctionEmptyState />
|
|
155
132
|
</div>
|
|
156
133
|
)}
|
|
134
|
+
</div>
|
|
157
135
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
</div>
|
|
136
|
+
{/* Loading mask overlay */}
|
|
137
|
+
{isRefreshing && (
|
|
138
|
+
<div className="absolute inset-0 bg-white dark:bg-neutral-800 flex items-center justify-center z-50">
|
|
139
|
+
<div className="flex items-center gap-1">
|
|
140
|
+
<div className="w-5 h-5 border-2 border-zinc-500 dark:border-neutral-700 border-t-transparent rounded-full animate-spin" />
|
|
141
|
+
<span className="text-sm text-zinc-500 dark:text-zinc-400">Loading</span>
|
|
165
142
|
</div>
|
|
166
|
-
|
|
167
|
-
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
168
145
|
</div>
|
|
169
146
|
</div>
|
|
170
147
|
);
|
|
@@ -75,16 +75,18 @@ export default function SecretsPage() {
|
|
|
75
75
|
className="max-w-70 dark:bg-neutral-900 dark:border-neutral-700"
|
|
76
76
|
/>
|
|
77
77
|
|
|
78
|
-
{/* Secrets Table */}
|
|
79
|
-
<div className="
|
|
80
|
-
|
|
81
|
-
<div className="
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
</div>
|
|
78
|
+
{/* Secrets Table Header */}
|
|
79
|
+
<div className="grid grid-cols-12 px-3 text-sm text-muted-foreground dark:text-neutral-400">
|
|
80
|
+
<div className="col-span-8 py-1 px-3">Name</div>
|
|
81
|
+
{/* <div className="col-span-5 py-1 px-3">Digest</div> */}
|
|
82
|
+
<div className="col-span-3 py-1 px-3">Updated at</div>
|
|
83
|
+
<div className="col-span-1 py-1 px-3" />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
87
86
|
|
|
87
|
+
{/* Scrollable Table Body */}
|
|
88
|
+
<div className="flex-1 min-h-0 overflow-y-auto px-4 pb-4">
|
|
89
|
+
<div className="flex flex-col gap-2">
|
|
88
90
|
{loading ? (
|
|
89
91
|
<>
|
|
90
92
|
{[...Array(4)].map((_, i) => (
|
|
@@ -1,40 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useQuery
|
|
3
|
-
import { useSocket, ServerEvents } from '@/lib/contexts/SocketContext';
|
|
1
|
+
import { useMemo, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
3
|
import { useAuth } from '@/lib/contexts/AuthContext';
|
|
5
4
|
import { usageService, McpUsageRecord } from '@/features/logs/services/usage.service';
|
|
6
5
|
import { isInsForgeCloudProject } from '@/lib/utils/utils';
|
|
7
|
-
import { LOGS_PAGE_SIZE } from '../helpers';
|
|
8
6
|
import { postMessageToParent } from '@/lib/utils/cloudMessaging';
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Types
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
export interface McpConnectedPayload {
|
|
15
|
-
tool_name: string;
|
|
16
|
-
created_at: string;
|
|
17
|
-
}
|
|
7
|
+
import { LOGS_PAGE_SIZE } from '../helpers';
|
|
18
8
|
|
|
19
9
|
// ============================================================================
|
|
20
10
|
// Main Hook
|
|
21
11
|
// ============================================================================
|
|
22
12
|
|
|
23
13
|
/**
|
|
24
|
-
* Hook to manage MCP usage data
|
|
14
|
+
* Hook to manage MCP usage data
|
|
25
15
|
*
|
|
26
16
|
* Features:
|
|
27
|
-
* - Fetches
|
|
28
|
-
* - Listens to real-time socket updates for new MCP calls
|
|
29
|
-
* - Invalidates queries on WebSocket events to refetch latest data
|
|
17
|
+
* - Fetches MCP logs from backend
|
|
30
18
|
* - Provides helper functions for data access
|
|
31
|
-
* - Handles parent window
|
|
19
|
+
* - Handles initial parent window notification for onboarding (if in iframe)
|
|
32
20
|
* - Supports search and pagination
|
|
21
|
+
*
|
|
33
22
|
*/
|
|
34
23
|
export function useMcpUsage() {
|
|
35
24
|
// Hooks
|
|
36
|
-
const queryClient = useQueryClient();
|
|
37
|
-
const { socket, isConnected } = useSocket();
|
|
38
25
|
const { isAuthenticated } = useAuth();
|
|
39
26
|
|
|
40
27
|
// State
|
|
@@ -100,54 +87,14 @@ export function useMcpUsage() {
|
|
|
100
87
|
hasNotifiedInitialStatus.current = true;
|
|
101
88
|
|
|
102
89
|
const latestRecord = records[0];
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
'*'
|
|
111
|
-
);
|
|
90
|
+
postMessageToParent({
|
|
91
|
+
type: 'MCP_CONNECTION_STATUS',
|
|
92
|
+
connected: true,
|
|
93
|
+
tool_name: latestRecord.tool_name,
|
|
94
|
+
timestamp: latestRecord.created_at,
|
|
95
|
+
});
|
|
112
96
|
}, [isLoading, records]);
|
|
113
97
|
|
|
114
|
-
// Handle real-time MCP connection events from socket
|
|
115
|
-
const handleMcpConnected = useCallback(
|
|
116
|
-
(data: { id: string; payload: McpConnectedPayload; timestamp: number; type: string }) => {
|
|
117
|
-
// Notify parent window with latest MCP call info
|
|
118
|
-
if (window.parent !== window) {
|
|
119
|
-
window.parent.postMessage(
|
|
120
|
-
{
|
|
121
|
-
type: 'MCP_CONNECTION_STATUS',
|
|
122
|
-
connected: true,
|
|
123
|
-
tool_name: data.payload.tool_name,
|
|
124
|
-
timestamp: data.payload.created_at,
|
|
125
|
-
},
|
|
126
|
-
'*'
|
|
127
|
-
);
|
|
128
|
-
if (!records.length) {
|
|
129
|
-
postMessageToParent({ type: 'ONBOARDING_SUCCESS' });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// Invalidate query to refetch latest data (follows codebase pattern)
|
|
133
|
-
void queryClient.invalidateQueries({ queryKey: ['mcp-usage'] });
|
|
134
|
-
},
|
|
135
|
-
[queryClient, records.length]
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
// Subscribe to socket MCP connection events
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
if (!socket || !isConnected) {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
socket.on(ServerEvents.MCP_CONNECTED, handleMcpConnected);
|
|
145
|
-
|
|
146
|
-
return () => {
|
|
147
|
-
socket.off(ServerEvents.MCP_CONNECTED, handleMcpConnected);
|
|
148
|
-
};
|
|
149
|
-
}, [socket, isConnected, handleMcpConnected]);
|
|
150
|
-
|
|
151
98
|
// Computed values
|
|
152
99
|
const hasCompletedOnboarding = useMemo(() => !!records.length, [records]);
|
|
153
100
|
const recordsCount = useMemo(() => records.length, [records]);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cn, formatDate } from '@/lib/utils/utils';
|
|
2
|
+
import { Trash2 } from 'lucide-react';
|
|
3
|
+
import { Switch } from '@/components';
|
|
4
|
+
import type { RealtimeChannel } from '../services/realtime.service';
|
|
5
|
+
|
|
6
|
+
interface ChannelRowProps {
|
|
7
|
+
channel: RealtimeChannel;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
onToggleEnabled: (enabled: boolean) => void;
|
|
10
|
+
onDelete: () => void;
|
|
11
|
+
isUpdating?: boolean;
|
|
12
|
+
isDeleting?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ChannelRow({
|
|
17
|
+
channel,
|
|
18
|
+
onClick,
|
|
19
|
+
onToggleEnabled,
|
|
20
|
+
onDelete,
|
|
21
|
+
isUpdating,
|
|
22
|
+
isDeleting,
|
|
23
|
+
className,
|
|
24
|
+
}: ChannelRowProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
'group flex items-center h-14 px-3 bg-white hover:bg-neutral-100 dark:bg-[#333333] dark:hover:bg-neutral-700 rounded-lg transition-all cursor-pointer',
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
onClick={onClick}
|
|
32
|
+
>
|
|
33
|
+
{/* Toggle Switch - matches header w-[76px] */}
|
|
34
|
+
<div className="flex items-center w-[76px] shrink-0 px-3 py-1.5">
|
|
35
|
+
<Switch
|
|
36
|
+
checked={channel.enabled}
|
|
37
|
+
disabled={isUpdating}
|
|
38
|
+
onCheckedChange={(checked) => {
|
|
39
|
+
onToggleEnabled(checked);
|
|
40
|
+
}}
|
|
41
|
+
onClick={(e) => e.stopPropagation()}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* Pattern Column - flex-1 to match header */}
|
|
46
|
+
<div className="flex-1 min-w-0 px-3 py-1.5">
|
|
47
|
+
<p className="text-sm text-zinc-950 dark:text-white truncate" title={channel.pattern}>
|
|
48
|
+
{channel.pattern}
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Description Column - fixed w-[640px] to match header */}
|
|
53
|
+
<div className="w-[640px] min-w-0 px-3 py-1.5">
|
|
54
|
+
<span
|
|
55
|
+
className="text-sm text-zinc-700 dark:text-white truncate block"
|
|
56
|
+
title={channel.description || ''}
|
|
57
|
+
>
|
|
58
|
+
{channel.description || '-'}
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Created Column - flex-1 to match header */}
|
|
63
|
+
<div className="flex-1 min-w-0 px-3 py-1">
|
|
64
|
+
<span className="text-sm text-zinc-700 dark:text-white truncate" title={channel.createdAt}>
|
|
65
|
+
{formatDate(channel.createdAt)}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{/* Delete Button - hidden by default, visible on hover */}
|
|
70
|
+
<button
|
|
71
|
+
className="flex items-center justify-center size-8 rounded opacity-0 group-hover:opacity-100 hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-all disabled:opacity-50"
|
|
72
|
+
onClick={(e) => {
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
onDelete();
|
|
75
|
+
}}
|
|
76
|
+
disabled={isDeleting}
|
|
77
|
+
aria-label="Delete channel"
|
|
78
|
+
>
|
|
79
|
+
<Trash2 className="size-5 text-neutral-400 group-hover:text-zinc-600 dark:group-hover:text-white transition-colors" />
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|