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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import RefreshIcon from '@/assets/icons/refresh.svg?react';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
ConfirmDialog,
|
|
6
|
+
Skeleton,
|
|
7
|
+
Tooltip,
|
|
8
|
+
TooltipContent,
|
|
9
|
+
TooltipProvider,
|
|
10
|
+
TooltipTrigger,
|
|
11
|
+
} from '@/components';
|
|
12
|
+
import { useConfirm } from '@/lib/hooks/useConfirm';
|
|
13
|
+
import { useRealtime } from '../hooks/useRealtime';
|
|
14
|
+
import { ChannelRow } from '../components/ChannelRow';
|
|
15
|
+
import { EditChannelModal } from '../components/EditChannelModal';
|
|
16
|
+
import RealtimeEmptyState from '../components/RealtimeEmptyState';
|
|
17
|
+
import type { RealtimeChannel } from '../services/realtime.service';
|
|
18
|
+
|
|
19
|
+
export default function RealtimeChannelsPage() {
|
|
20
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
21
|
+
const [selectedChannel, setSelectedChannel] = useState<RealtimeChannel | null>(null);
|
|
22
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
channels,
|
|
26
|
+
isLoadingChannels,
|
|
27
|
+
refetchChannels,
|
|
28
|
+
updateChannel,
|
|
29
|
+
isUpdating,
|
|
30
|
+
deleteChannel,
|
|
31
|
+
isDeleting,
|
|
32
|
+
} = useRealtime();
|
|
33
|
+
|
|
34
|
+
const { confirm, confirmDialogProps } = useConfirm();
|
|
35
|
+
|
|
36
|
+
const handleRefresh = async () => {
|
|
37
|
+
setIsRefreshing(true);
|
|
38
|
+
try {
|
|
39
|
+
await refetchChannels();
|
|
40
|
+
} finally {
|
|
41
|
+
setIsRefreshing(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleRowClick = (channel: RealtimeChannel) => {
|
|
46
|
+
setSelectedChannel(channel);
|
|
47
|
+
setIsModalOpen(true);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleToggleEnabled = (channel: RealtimeChannel, enabled: boolean) => {
|
|
51
|
+
updateChannel({ id: channel.id, data: { enabled } });
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleDelete = async (channel: RealtimeChannel) => {
|
|
55
|
+
const shouldDelete = await confirm({
|
|
56
|
+
title: 'Delete Channel',
|
|
57
|
+
description: `Are you sure you want to delete the channel "${channel.pattern}"? This action cannot be undone.`,
|
|
58
|
+
confirmText: 'Delete',
|
|
59
|
+
destructive: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (shouldDelete) {
|
|
63
|
+
deleteChannel(channel.id);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleModalSave = (id: string, data: Parameters<typeof updateChannel>[0]['data']) => {
|
|
68
|
+
updateChannel(
|
|
69
|
+
{ id, data },
|
|
70
|
+
{
|
|
71
|
+
onSuccess: () => {
|
|
72
|
+
setIsModalOpen(false);
|
|
73
|
+
setSelectedChannel(null);
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
return (
|
|
79
|
+
<div className="h-full flex flex-col overflow-hidden">
|
|
80
|
+
<div className="flex flex-col gap-6 p-4">
|
|
81
|
+
<div className="flex items-center gap-3">
|
|
82
|
+
<h1 className="text-xl font-normal text-zinc-950 dark:text-white">Channels</h1>
|
|
83
|
+
|
|
84
|
+
{/* Separator */}
|
|
85
|
+
<div className="h-6 w-px bg-gray-200 dark:bg-neutral-700" />
|
|
86
|
+
|
|
87
|
+
{/* Refresh button */}
|
|
88
|
+
<TooltipProvider>
|
|
89
|
+
<Tooltip>
|
|
90
|
+
<TooltipTrigger asChild>
|
|
91
|
+
<Button
|
|
92
|
+
variant="ghost"
|
|
93
|
+
size="icon"
|
|
94
|
+
className="p-1 h-9 w-9"
|
|
95
|
+
onClick={() => void handleRefresh()}
|
|
96
|
+
disabled={isRefreshing}
|
|
97
|
+
>
|
|
98
|
+
<RefreshIcon className="h-5 w-5 text-zinc-400 dark:text-neutral-400" />
|
|
99
|
+
</Button>
|
|
100
|
+
</TooltipTrigger>
|
|
101
|
+
<TooltipContent side="bottom" align="center">
|
|
102
|
+
<p>{isRefreshing ? 'Refreshing...' : 'Refresh'}</p>
|
|
103
|
+
</TooltipContent>
|
|
104
|
+
</Tooltip>
|
|
105
|
+
</TooltipProvider>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Table Header */}
|
|
109
|
+
<div className="flex items-center pl-3 pr-[44px] text-sm text-muted-foreground dark:text-neutral-400">
|
|
110
|
+
<div className="w-[76px] shrink-0 py-1 px-3">Enabled</div>
|
|
111
|
+
<div className="flex-1 py-1 px-3">Pattern</div>
|
|
112
|
+
<div className="w-[640px] py-1 px-3">Description</div>
|
|
113
|
+
<div className="flex-1 py-1 px-3">Created</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Scrollable Table Body */}
|
|
118
|
+
<div className="flex-1 min-h-0 overflow-y-auto px-4 pb-4 relative">
|
|
119
|
+
<div className="flex flex-col gap-2">
|
|
120
|
+
{isLoadingChannels ? (
|
|
121
|
+
<>
|
|
122
|
+
{[...Array(4)].map((_, i) => (
|
|
123
|
+
<Skeleton key={i} className="h-14 rounded-[8px]" />
|
|
124
|
+
))}
|
|
125
|
+
</>
|
|
126
|
+
) : channels.length >= 1 ? (
|
|
127
|
+
<>
|
|
128
|
+
{channels.map((channel) => (
|
|
129
|
+
<ChannelRow
|
|
130
|
+
key={channel.id}
|
|
131
|
+
channel={channel}
|
|
132
|
+
onClick={() => handleRowClick(channel)}
|
|
133
|
+
onToggleEnabled={(enabled) => handleToggleEnabled(channel, enabled)}
|
|
134
|
+
onDelete={() => void handleDelete(channel)}
|
|
135
|
+
isUpdating={isUpdating}
|
|
136
|
+
isDeleting={isDeleting}
|
|
137
|
+
/>
|
|
138
|
+
))}
|
|
139
|
+
</>
|
|
140
|
+
) : (
|
|
141
|
+
<RealtimeEmptyState type="channels" />
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{/* Loading mask overlay */}
|
|
146
|
+
{isRefreshing && (
|
|
147
|
+
<div className="absolute inset-0 bg-white dark:bg-neutral-800 flex items-center justify-center z-50">
|
|
148
|
+
<div className="flex items-center gap-1">
|
|
149
|
+
<div className="w-5 h-5 border-2 border-zinc-500 dark:border-neutral-700 border-t-transparent rounded-full animate-spin" />
|
|
150
|
+
<span className="text-sm text-zinc-500 dark:text-zinc-400">Loading</span>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<EditChannelModal
|
|
157
|
+
channel={selectedChannel}
|
|
158
|
+
open={isModalOpen}
|
|
159
|
+
onOpenChange={(open) => {
|
|
160
|
+
setIsModalOpen(open);
|
|
161
|
+
if (!open) {
|
|
162
|
+
setSelectedChannel(null);
|
|
163
|
+
}
|
|
164
|
+
}}
|
|
165
|
+
onSave={handleModalSave}
|
|
166
|
+
isUpdating={isUpdating}
|
|
167
|
+
/>
|
|
168
|
+
|
|
169
|
+
<ConfirmDialog {...confirmDialogProps} />
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ChevronRight } from 'lucide-react';
|
|
3
|
+
import RefreshIcon from '@/assets/icons/refresh.svg?react';
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
PaginationControls,
|
|
7
|
+
Skeleton,
|
|
8
|
+
Tooltip,
|
|
9
|
+
TooltipContent,
|
|
10
|
+
TooltipProvider,
|
|
11
|
+
TooltipTrigger,
|
|
12
|
+
} from '@/components';
|
|
13
|
+
import { useRealtime } from '../hooks/useRealtime';
|
|
14
|
+
import { MessageRow } from '../components/MessageRow';
|
|
15
|
+
import RealtimeEmptyState from '../components/RealtimeEmptyState';
|
|
16
|
+
import type { RealtimeMessage } from '../services/realtime.service';
|
|
17
|
+
|
|
18
|
+
export default function RealtimeMessagesPage() {
|
|
19
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
20
|
+
const [selectedMessage, setSelectedMessage] = useState<RealtimeMessage | null>(null);
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
messages,
|
|
24
|
+
isLoadingMessages,
|
|
25
|
+
refetchMessages,
|
|
26
|
+
messagesPageSize,
|
|
27
|
+
messagesCurrentPage,
|
|
28
|
+
messagesTotalCount,
|
|
29
|
+
messagesTotalPages,
|
|
30
|
+
setMessagesPage,
|
|
31
|
+
} = useRealtime();
|
|
32
|
+
|
|
33
|
+
const handleRefresh = async () => {
|
|
34
|
+
setIsRefreshing(true);
|
|
35
|
+
try {
|
|
36
|
+
await refetchMessages();
|
|
37
|
+
} finally {
|
|
38
|
+
setIsRefreshing(false);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Message detail view
|
|
43
|
+
if (selectedMessage) {
|
|
44
|
+
return (
|
|
45
|
+
<div className="h-full flex flex-col overflow-hidden">
|
|
46
|
+
<div className="flex items-center gap-2.5 p-4 border-b border-border-gray dark:border-neutral-600">
|
|
47
|
+
<button
|
|
48
|
+
onClick={() => setSelectedMessage(null)}
|
|
49
|
+
className="text-xl text-zinc-500 dark:text-neutral-400 hover:text-zinc-950 dark:hover:text-white transition-colors"
|
|
50
|
+
>
|
|
51
|
+
Messages
|
|
52
|
+
</button>
|
|
53
|
+
<ChevronRight className="w-5 h-5 text-muted-foreground dark:text-neutral-400" />
|
|
54
|
+
<p className="text-xl text-zinc-950 dark:text-white">{selectedMessage.eventName}</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="flex-1 min-h-0 p-4 overflow-auto">
|
|
58
|
+
<div className="space-y-4">
|
|
59
|
+
<div className="grid grid-cols-3 gap-4">
|
|
60
|
+
<div className="p-4 rounded-lg bg-neutral-100 dark:bg-[#333333]">
|
|
61
|
+
<p className="text-sm text-muted-foreground dark:text-neutral-400 mb-1">Channel</p>
|
|
62
|
+
<p className="text-sm text-zinc-950 dark:text-white">
|
|
63
|
+
{selectedMessage.channelName}
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
<div className="p-4 rounded-lg bg-neutral-100 dark:bg-[#333333]">
|
|
67
|
+
<p className="text-sm text-muted-foreground dark:text-neutral-400 mb-1">
|
|
68
|
+
Sender Type
|
|
69
|
+
</p>
|
|
70
|
+
<p className="text-sm text-zinc-950 dark:text-white">
|
|
71
|
+
{selectedMessage.senderType}
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="p-4 rounded-lg bg-neutral-100 dark:bg-[#333333]">
|
|
75
|
+
<p className="text-sm text-muted-foreground dark:text-neutral-400 mb-1">Created</p>
|
|
76
|
+
<p className="text-sm text-zinc-950 dark:text-white">{selectedMessage.createdAt}</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="grid grid-cols-3 gap-4">
|
|
81
|
+
<div className="p-4 rounded-lg bg-neutral-100 dark:bg-[#333333]">
|
|
82
|
+
<p className="text-sm text-muted-foreground dark:text-neutral-400 mb-1">
|
|
83
|
+
WS Audience
|
|
84
|
+
</p>
|
|
85
|
+
<p className="text-sm text-zinc-950 dark:text-white">
|
|
86
|
+
{selectedMessage.wsAudienceCount}
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="p-4 rounded-lg bg-neutral-100 dark:bg-[#333333]">
|
|
90
|
+
<p className="text-sm text-muted-foreground dark:text-neutral-400 mb-1">
|
|
91
|
+
WH Audience
|
|
92
|
+
</p>
|
|
93
|
+
<p className="text-sm text-zinc-950 dark:text-white">
|
|
94
|
+
{selectedMessage.whAudienceCount}
|
|
95
|
+
</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="p-4 rounded-lg bg-neutral-100 dark:bg-[#333333]">
|
|
98
|
+
<p className="text-sm text-muted-foreground dark:text-neutral-400 mb-1">
|
|
99
|
+
WH Delivered
|
|
100
|
+
</p>
|
|
101
|
+
<p className="text-sm text-zinc-950 dark:text-white">
|
|
102
|
+
{selectedMessage.whDeliveredCount}
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="p-4 rounded-lg bg-neutral-100 dark:bg-[#333333]">
|
|
108
|
+
<p className="text-sm text-muted-foreground dark:text-neutral-400 mb-2">Payload</p>
|
|
109
|
+
<pre className="text-sm text-zinc-950 dark:text-white font-mono whitespace-pre-wrap overflow-auto">
|
|
110
|
+
{JSON.stringify(selectedMessage.payload, null, 2)}
|
|
111
|
+
</pre>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Default list view
|
|
120
|
+
return (
|
|
121
|
+
<div className="h-full flex flex-col overflow-hidden">
|
|
122
|
+
{/* Fixed Page Header */}
|
|
123
|
+
<div className="shrink-0 flex items-center gap-3 p-4 pb-0">
|
|
124
|
+
<h1 className="text-xl font-normal text-zinc-950 dark:text-white">Messages</h1>
|
|
125
|
+
|
|
126
|
+
{/* Separator */}
|
|
127
|
+
<div className="h-6 w-px bg-gray-200 dark:bg-neutral-700" />
|
|
128
|
+
|
|
129
|
+
{/* Refresh button */}
|
|
130
|
+
<TooltipProvider>
|
|
131
|
+
<Tooltip>
|
|
132
|
+
<TooltipTrigger asChild>
|
|
133
|
+
<Button
|
|
134
|
+
variant="ghost"
|
|
135
|
+
size="icon"
|
|
136
|
+
className="p-1 h-9 w-9"
|
|
137
|
+
onClick={() => void handleRefresh()}
|
|
138
|
+
disabled={isRefreshing}
|
|
139
|
+
>
|
|
140
|
+
<RefreshIcon className="h-5 w-5 text-zinc-400 dark:text-neutral-400" />
|
|
141
|
+
</Button>
|
|
142
|
+
</TooltipTrigger>
|
|
143
|
+
<TooltipContent side="bottom" align="center">
|
|
144
|
+
<p>{isRefreshing ? 'Refreshing...' : 'Refresh'}</p>
|
|
145
|
+
</TooltipContent>
|
|
146
|
+
</Tooltip>
|
|
147
|
+
</TooltipProvider>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{/* Fixed Table Header */}
|
|
151
|
+
<div className="shrink-0 grid grid-cols-12 px-7 pt-6 pb-2 text-sm text-muted-foreground dark:text-neutral-400">
|
|
152
|
+
<div className="col-span-2 py-1 px-3">Event</div>
|
|
153
|
+
<div className="col-span-2 py-1 px-3">Channel</div>
|
|
154
|
+
<div className="col-span-1 py-1 px-3">Sender</div>
|
|
155
|
+
<div className="col-span-3 py-1 px-3">Payload</div>
|
|
156
|
+
<div className="col-span-1 py-1 px-3">WebSockets</div>
|
|
157
|
+
<div className="col-span-1 py-1 px-3">Webhooks</div>
|
|
158
|
+
<div className="col-span-2 py-1 px-3">Sent At</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
{/* Scrollable Content Area */}
|
|
162
|
+
<div className="flex-1 min-h-0 overflow-auto px-4 pb-4 relative">
|
|
163
|
+
<div className="flex flex-col gap-2">
|
|
164
|
+
{isLoadingMessages ? (
|
|
165
|
+
<>
|
|
166
|
+
{[...Array(4)].map((_, i) => (
|
|
167
|
+
<Skeleton key={i} className="h-14 rounded-[8px]" />
|
|
168
|
+
))}
|
|
169
|
+
</>
|
|
170
|
+
) : messages.length >= 1 ? (
|
|
171
|
+
<>
|
|
172
|
+
{messages.map((message) => (
|
|
173
|
+
<MessageRow
|
|
174
|
+
key={message.id}
|
|
175
|
+
message={message}
|
|
176
|
+
onClick={() => setSelectedMessage(message)}
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
179
|
+
</>
|
|
180
|
+
) : (
|
|
181
|
+
<RealtimeEmptyState type="messages" />
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Loading mask overlay */}
|
|
186
|
+
{isRefreshing && (
|
|
187
|
+
<div className="absolute inset-0 bg-white dark:bg-neutral-800 flex items-center justify-center z-50">
|
|
188
|
+
<div className="flex items-center gap-1">
|
|
189
|
+
<div className="w-5 h-5 border-2 border-zinc-500 dark:border-neutral-700 border-t-transparent rounded-full animate-spin" />
|
|
190
|
+
<span className="text-sm text-zinc-500 dark:text-zinc-400">Loading</span>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
{/* Pagination */}
|
|
197
|
+
{messages.length > 0 && (
|
|
198
|
+
<div className="shrink-0">
|
|
199
|
+
<PaginationControls
|
|
200
|
+
currentPage={messagesCurrentPage}
|
|
201
|
+
totalPages={messagesTotalPages}
|
|
202
|
+
onPageChange={setMessagesPage}
|
|
203
|
+
totalRecords={messagesTotalCount}
|
|
204
|
+
pageSize={messagesPageSize}
|
|
205
|
+
recordLabel="messages"
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
DataGrid,
|
|
4
|
+
type ConvertedValue,
|
|
5
|
+
type DataGridColumn,
|
|
6
|
+
type DataGridRowType,
|
|
7
|
+
EmptyState,
|
|
8
|
+
} from '@/components';
|
|
9
|
+
import { SQLModal, SQLCellButton } from '@/features/database';
|
|
10
|
+
import { useRealtime } from '../hooks/useRealtime';
|
|
11
|
+
import type { RlsPolicy } from '../services/realtime.service';
|
|
12
|
+
import { cn } from '@/lib/utils/utils';
|
|
13
|
+
|
|
14
|
+
type TabType = 'subscribe' | 'publish';
|
|
15
|
+
|
|
16
|
+
interface PolicyRow extends DataGridRowType {
|
|
17
|
+
id: string;
|
|
18
|
+
policyName: string;
|
|
19
|
+
command: string;
|
|
20
|
+
roles: string;
|
|
21
|
+
using: string | null;
|
|
22
|
+
withCheck: string | null;
|
|
23
|
+
[key: string]: ConvertedValue | { [key: string]: string }[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mapPoliciesToRows(policies: RlsPolicy[]): PolicyRow[] {
|
|
27
|
+
return policies.map((policy, index) => ({
|
|
28
|
+
id: `${policy.tableName}_${policy.policyName}_${index}`,
|
|
29
|
+
policyName: policy.policyName,
|
|
30
|
+
command: policy.command === '*' ? 'ALL' : policy.command,
|
|
31
|
+
roles: Array.isArray(policy.roles) ? policy.roles.join(', ') : String(policy.roles),
|
|
32
|
+
using: policy.using,
|
|
33
|
+
withCheck: policy.withCheck,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function RealtimePermissionsPage() {
|
|
38
|
+
const [activeTab, setActiveTab] = useState<TabType>('subscribe');
|
|
39
|
+
const [sqlModal, setSqlModal] = useState({ open: false, title: '', value: '' });
|
|
40
|
+
|
|
41
|
+
const { permissions, isLoadingPermissions: isLoading, permissionsError: error } = useRealtime();
|
|
42
|
+
|
|
43
|
+
const subscribePolicies = useMemo(
|
|
44
|
+
() => (permissions ? mapPoliciesToRows(permissions.subscribe.policies) : []),
|
|
45
|
+
[permissions]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const publishPolicies = useMemo(
|
|
49
|
+
() => (permissions ? mapPoliciesToRows(permissions.publish.policies) : []),
|
|
50
|
+
[permissions]
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const activePolicies = activeTab === 'subscribe' ? subscribePolicies : publishPolicies;
|
|
54
|
+
|
|
55
|
+
const columns: DataGridColumn<PolicyRow>[] = useMemo(
|
|
56
|
+
() => [
|
|
57
|
+
{
|
|
58
|
+
key: 'policyName',
|
|
59
|
+
name: 'Policy Name',
|
|
60
|
+
width: 'minmax(200px, 2fr)',
|
|
61
|
+
resizable: true,
|
|
62
|
+
sortable: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: 'command',
|
|
66
|
+
name: 'Command',
|
|
67
|
+
width: 'minmax(100px, 1fr)',
|
|
68
|
+
resizable: true,
|
|
69
|
+
sortable: true,
|
|
70
|
+
renderCell: ({ row }) => {
|
|
71
|
+
return (
|
|
72
|
+
<span className="inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-slate-600 text-white">
|
|
73
|
+
{row.command}
|
|
74
|
+
</span>
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: 'roles',
|
|
80
|
+
name: 'Roles',
|
|
81
|
+
width: 'minmax(150px, 1.5fr)',
|
|
82
|
+
resizable: true,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'using',
|
|
86
|
+
name: 'Using',
|
|
87
|
+
width: 'minmax(200px, 2fr)',
|
|
88
|
+
resizable: true,
|
|
89
|
+
renderCell: ({ row }) => (
|
|
90
|
+
<SQLCellButton
|
|
91
|
+
value={row.using}
|
|
92
|
+
onClick={() =>
|
|
93
|
+
row.using && setSqlModal({ open: true, title: 'Using', value: row.using })
|
|
94
|
+
}
|
|
95
|
+
/>
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: 'withCheck',
|
|
100
|
+
name: 'With Check',
|
|
101
|
+
width: 'minmax(200px, 2fr)',
|
|
102
|
+
resizable: true,
|
|
103
|
+
renderCell: ({ row }) => (
|
|
104
|
+
<SQLCellButton
|
|
105
|
+
value={row.withCheck}
|
|
106
|
+
onClick={() =>
|
|
107
|
+
row.withCheck &&
|
|
108
|
+
setSqlModal({ open: true, title: 'With Check', value: row.withCheck })
|
|
109
|
+
}
|
|
110
|
+
/>
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
[setSqlModal]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (error) {
|
|
118
|
+
return (
|
|
119
|
+
<div className="flex-1 flex items-center justify-center">
|
|
120
|
+
<EmptyState
|
|
121
|
+
title="Failed to load permissions"
|
|
122
|
+
description={error instanceof Error ? error.message : 'An error occurred'}
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="h-full flex flex-col overflow-hidden">
|
|
130
|
+
{/* Fixed Header */}
|
|
131
|
+
<div className="shrink-0 bg-bg-gray dark:bg-neutral-800 p-4 flex flex-col gap-6">
|
|
132
|
+
{/* Title */}
|
|
133
|
+
<h1 className="text-xl font-normal text-zinc-950 dark:text-white">Permissions</h1>
|
|
134
|
+
|
|
135
|
+
{/* Tabs */}
|
|
136
|
+
<div className="flex gap-6 items-start">
|
|
137
|
+
<button
|
|
138
|
+
onClick={() => setActiveTab('subscribe')}
|
|
139
|
+
className={cn(
|
|
140
|
+
'h-8 text-sm font-medium transition-colors',
|
|
141
|
+
activeTab === 'subscribe'
|
|
142
|
+
? 'text-zinc-950 dark:text-white border-b-2 border-zinc-950 dark:border-white'
|
|
143
|
+
: 'text-zinc-500 dark:text-neutral-400 hover:text-zinc-700 dark:hover:text-neutral-300'
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
Subscribe Policies
|
|
147
|
+
</button>
|
|
148
|
+
<button
|
|
149
|
+
onClick={() => setActiveTab('publish')}
|
|
150
|
+
className={cn(
|
|
151
|
+
'h-8 text-sm font-medium transition-colors',
|
|
152
|
+
activeTab === 'publish'
|
|
153
|
+
? 'text-zinc-950 dark:text-white border-b-2 border-zinc-950 dark:border-white'
|
|
154
|
+
: 'text-zinc-500 dark:text-neutral-400 hover:text-zinc-700 dark:hover:text-neutral-300'
|
|
155
|
+
)}
|
|
156
|
+
>
|
|
157
|
+
Publish Policies
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{/* Content */}
|
|
163
|
+
<div className="flex-1 min-h-0 overflow-hidden px-3 pb-2">
|
|
164
|
+
{isLoading ? (
|
|
165
|
+
<div className="flex items-center justify-center h-full">
|
|
166
|
+
<EmptyState title="Loading policies..." description="Please wait" />
|
|
167
|
+
</div>
|
|
168
|
+
) : (
|
|
169
|
+
<DataGrid
|
|
170
|
+
data={activePolicies}
|
|
171
|
+
columns={columns}
|
|
172
|
+
showSelection={false}
|
|
173
|
+
showPagination={false}
|
|
174
|
+
noPadding={true}
|
|
175
|
+
emptyState={
|
|
176
|
+
<div className="text-sm text-zinc-500 dark:text-zinc-400">No policies defined</div>
|
|
177
|
+
}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* SQL Detail Modal */}
|
|
183
|
+
<SQLModal
|
|
184
|
+
open={sqlModal.open}
|
|
185
|
+
onOpenChange={(open) => setSqlModal((prev) => ({ ...prev, open }))}
|
|
186
|
+
title={sqlModal.title}
|
|
187
|
+
value={sqlModal.value}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { apiClient } from '@/lib/api/client';
|
|
2
|
+
import type {
|
|
3
|
+
RealtimeChannel,
|
|
4
|
+
RealtimeMessage,
|
|
5
|
+
CreateChannelRequest,
|
|
6
|
+
UpdateChannelRequest,
|
|
7
|
+
ListMessagesRequest,
|
|
8
|
+
MessageStatsResponse,
|
|
9
|
+
RlsPolicy,
|
|
10
|
+
RealtimePermissionsResponse,
|
|
11
|
+
} from '@insforge/shared-schemas';
|
|
12
|
+
|
|
13
|
+
export type { RealtimeChannel, RealtimeMessage, RlsPolicy, RealtimePermissionsResponse };
|
|
14
|
+
|
|
15
|
+
export class RealtimeService {
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Channels
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
async listChannels(): Promise<RealtimeChannel[]> {
|
|
21
|
+
return apiClient.request('/realtime/channels', {
|
|
22
|
+
headers: apiClient.withAccessToken(),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getChannel(id: string): Promise<RealtimeChannel> {
|
|
27
|
+
return apiClient.request(`/realtime/channels/${id}`, {
|
|
28
|
+
headers: apiClient.withAccessToken(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async createChannel(data: CreateChannelRequest): Promise<RealtimeChannel> {
|
|
33
|
+
return apiClient.request('/realtime/channels', {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: apiClient.withAccessToken(),
|
|
36
|
+
body: JSON.stringify(data),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async updateChannel(id: string, data: UpdateChannelRequest): Promise<RealtimeChannel> {
|
|
41
|
+
return apiClient.request(`/realtime/channels/${id}`, {
|
|
42
|
+
method: 'PUT',
|
|
43
|
+
headers: apiClient.withAccessToken(),
|
|
44
|
+
body: JSON.stringify(data),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async deleteChannel(id: string): Promise<void> {
|
|
49
|
+
return apiClient.request(`/realtime/channels/${id}`, {
|
|
50
|
+
method: 'DELETE',
|
|
51
|
+
headers: apiClient.withAccessToken(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Messages
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
async listMessages(params?: ListMessagesRequest): Promise<RealtimeMessage[]> {
|
|
60
|
+
const searchParams = new URLSearchParams();
|
|
61
|
+
if (params?.channelId) {
|
|
62
|
+
searchParams.set('channelId', params.channelId);
|
|
63
|
+
}
|
|
64
|
+
if (params?.eventName) {
|
|
65
|
+
searchParams.set('eventName', params.eventName);
|
|
66
|
+
}
|
|
67
|
+
if (params?.limit) {
|
|
68
|
+
searchParams.set('limit', String(params.limit));
|
|
69
|
+
}
|
|
70
|
+
if (params?.offset) {
|
|
71
|
+
searchParams.set('offset', String(params.offset));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const query = searchParams.toString();
|
|
75
|
+
const endpoint = `/realtime/messages${query ? `?${query}` : ''}`;
|
|
76
|
+
|
|
77
|
+
return apiClient.request(endpoint, {
|
|
78
|
+
headers: apiClient.withAccessToken(),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getMessageStats(channelId?: string): Promise<MessageStatsResponse> {
|
|
83
|
+
const searchParams = new URLSearchParams();
|
|
84
|
+
if (channelId) {
|
|
85
|
+
searchParams.set('channelId', channelId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const query = searchParams.toString();
|
|
89
|
+
const endpoint = `/realtime/messages/stats${query ? `?${query}` : ''}`;
|
|
90
|
+
|
|
91
|
+
return apiClient.request(endpoint, {
|
|
92
|
+
headers: apiClient.withAccessToken(),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Permissions
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
async getPermissions(): Promise<RealtimePermissionsResponse> {
|
|
101
|
+
return apiClient.request('/realtime/permissions', {
|
|
102
|
+
headers: apiClient.withAccessToken(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const realtimeService = new RealtimeService();
|