@useatlas/create 0.0.1 → 0.0.3
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/README.md +4 -18
- package/index.ts +191 -31
- package/package.json +1 -1
- package/templates/docker/.env.example +3 -3
- package/templates/docker/Dockerfile.sidecar +28 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +11 -11
- package/templates/docker/bin/atlas.ts +120 -56
- package/templates/docker/data/demo-semantic/catalog.yml +51 -27
- package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
- package/templates/docker/data/demo-semantic/glossary.yml +104 -8
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/docker/docker-compose.yml +1 -1
- package/templates/docker/docs/deploy.md +4 -41
- package/templates/docker/package.json +17 -1
- package/templates/docker/semantic/catalog.yml +62 -3
- package/templates/docker/semantic/entities/accounts.yml +162 -0
- package/templates/docker/semantic/entities/companies.yml +143 -0
- package/templates/docker/semantic/entities/people.yml +132 -0
- package/templates/docker/semantic/glossary.yml +116 -4
- package/templates/docker/semantic/metrics/accounts.yml +77 -0
- package/templates/docker/semantic/metrics/companies.yml +63 -0
- package/templates/docker/sidecar/Dockerfile +5 -6
- package/templates/docker/sidecar/railway.json +1 -2
- package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/docker/src/api/__tests__/health.test.ts +30 -8
- package/templates/docker/src/api/routes/admin.ts +549 -8
- package/templates/docker/src/api/routes/chat.ts +5 -20
- package/templates/docker/src/api/routes/health.ts +39 -27
- package/templates/docker/src/api/routes/openapi.ts +1329 -74
- package/templates/docker/src/api/routes/query.ts +2 -1
- package/templates/docker/src/api/server.ts +27 -0
- package/templates/docker/src/app/api/[...route]/route.ts +2 -2
- package/templates/docker/src/app/globals.css +13 -12
- package/templates/docker/src/app/layout.tsx +9 -2
- package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/docker/src/components/ui/badge.tsx +48 -0
- package/templates/docker/src/components/ui/button.tsx +64 -0
- package/templates/docker/src/components/ui/card.tsx +92 -0
- package/templates/docker/src/components/ui/collapsible.tsx +33 -0
- package/templates/docker/src/components/ui/command.tsx +184 -0
- package/templates/docker/src/components/ui/dialog.tsx +158 -0
- package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/docker/src/components/ui/input.tsx +21 -0
- package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
- package/templates/docker/src/components/ui/select.tsx +190 -0
- package/templates/docker/src/components/ui/separator.tsx +28 -0
- package/templates/docker/src/components/ui/sheet.tsx +143 -0
- package/templates/docker/src/components/ui/sidebar.tsx +726 -0
- package/templates/docker/src/components/ui/skeleton.tsx +13 -0
- package/templates/docker/src/components/ui/table.tsx +116 -0
- package/templates/docker/src/components/ui/tabs.tsx +91 -0
- package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
- package/templates/docker/src/components/ui/toggle.tsx +47 -0
- package/templates/docker/src/components/ui/tooltip.tsx +57 -0
- package/templates/docker/src/hooks/use-mobile.ts +19 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
- package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/docker/src/lib/agent-query.ts +5 -23
- package/templates/docker/src/lib/agent.ts +32 -112
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/docker/src/lib/auth/middleware.ts +30 -4
- package/templates/docker/src/lib/auth/migrate.ts +97 -0
- package/templates/docker/src/lib/auth/server.ts +12 -1
- package/templates/docker/src/lib/config.ts +38 -40
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/docker/src/lib/db/connection.ts +87 -265
- package/templates/docker/src/lib/db/internal.ts +6 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/docker/src/lib/plugins/index.ts +4 -1
- package/templates/docker/src/lib/plugins/migrate.ts +104 -1
- package/templates/docker/src/lib/plugins/registry.ts +14 -8
- package/templates/docker/src/lib/plugins/wiring.ts +113 -4
- package/templates/docker/src/lib/providers.ts +6 -1
- package/templates/docker/src/lib/security.ts +24 -0
- package/templates/docker/src/lib/semantic.ts +2 -0
- package/templates/docker/src/lib/sidecar-types.ts +12 -1
- package/templates/docker/src/lib/startup.ts +71 -101
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/docker/src/lib/tools/explore.ts +28 -32
- package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/docker/src/lib/tools/python.ts +367 -0
- package/templates/docker/src/lib/tools/registry.ts +49 -22
- package/templates/docker/src/lib/tools/sql.ts +88 -88
- package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/docker/src/ui/context.tsx +1 -1
- package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/docker/tsconfig.json +2 -2
- package/templates/nextjs-standalone/.env.example +1 -1
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +11 -11
- package/templates/nextjs-standalone/bin/atlas.ts +120 -56
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/nextjs-standalone/docs/deploy.md +4 -41
- package/templates/nextjs-standalone/package.json +11 -2
- package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
- package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
- package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
- package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
- package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
- package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
- package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
- package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
- package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
- package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
- package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
- package/templates/nextjs-standalone/src/api/server.ts +27 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
- package/templates/nextjs-standalone/src/app/globals.css +13 -12
- package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
- package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
- package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
- package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
- package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
- package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
- package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
- package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
- package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
- package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
- package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
- package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
- package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
- package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
- package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
- package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
- package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
- package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
- package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
- package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
- package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
- package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
- package/templates/nextjs-standalone/src/lib/config.ts +38 -40
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
- package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +104 -1
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +14 -8
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
- package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
- package/templates/nextjs-standalone/src/lib/security.ts +24 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
- package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
- package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/nextjs-standalone/tsconfig.json +0 -1
- package/templates/nextjs-standalone/vercel.json +4 -1
- package/templates/docker/render.yaml +0 -34
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/docker/src/lib/db/duckdb.ts +0 -122
- package/templates/docker/src/lib/db/salesforce.ts +0 -342
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/docker/src/lib/tools/salesforce.ts +0 -138
- package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
|
@@ -31,9 +31,29 @@ const log = createLogger("sql");
|
|
|
31
31
|
|
|
32
32
|
const parser = new Parser();
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Strip SQL comments for regex guard testing.
|
|
36
|
+
*
|
|
37
|
+
* Block comments and line comments are removed so that anchored patterns
|
|
38
|
+
* like `^\s*(KILL)\b` cannot be bypassed with a leading comment.
|
|
39
|
+
*
|
|
40
|
+
* Only used for regex testing — the original SQL is passed to the AST parser
|
|
41
|
+
* unchanged so that comment-aware parsing still works correctly.
|
|
42
|
+
*/
|
|
43
|
+
function stripSqlComments(sql: string): string {
|
|
44
|
+
// Single regex handles string literals, block comments, line comments,
|
|
45
|
+
// and MySQL-style # comments in one pass. String literals (single-quoted
|
|
46
|
+
// with '' escape) are preserved unchanged; comments are replaced with a space.
|
|
47
|
+
return sql
|
|
48
|
+
.replace(/'(?:[^']|'')*'|\/\*[\s\S]*?\*\/|--[^\n]*|#[^\n]*/g, (match) =>
|
|
49
|
+
match.startsWith("'") ? match : " ",
|
|
50
|
+
)
|
|
51
|
+
.trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
const FORBIDDEN_PATTERNS = [
|
|
35
55
|
/\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b/i,
|
|
36
|
-
/\b(GRANT|REVOKE|EXEC|EXECUTE|CALL)\b/i,
|
|
56
|
+
/\b(GRANT|REVOKE|EXEC|EXECUTE|CALL|KILL)\b/i,
|
|
37
57
|
/\b(COPY|LOAD|VACUUM|REINDEX|OPTIMIZE)\b/i,
|
|
38
58
|
/\bINTO\s+OUTFILE\b/i,
|
|
39
59
|
];
|
|
@@ -45,82 +65,68 @@ const MYSQL_FORBIDDEN_PATTERNS = [
|
|
|
45
65
|
/\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
|
|
46
66
|
];
|
|
47
67
|
|
|
48
|
-
// ClickHouse-specific patterns — admin/mutation commands unique to ClickHouse
|
|
49
|
-
const CLICKHOUSE_FORBIDDEN_PATTERNS = [
|
|
50
|
-
/\b(SYSTEM)\b/i,
|
|
51
|
-
/\b(KILL)\b/i,
|
|
52
|
-
/\b(ATTACH|DETACH)\b/i,
|
|
53
|
-
/\b(RENAME)\b/i,
|
|
54
|
-
/\b(EXCHANGE)\b/i,
|
|
55
|
-
/\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
// Snowflake-specific patterns — only applied when dbType === "snowflake"
|
|
59
|
-
// Stage operations (PUT/GET/LIST/REMOVE/RM) and MERGE are Snowflake DML
|
|
60
|
-
// not covered by the base patterns.
|
|
61
|
-
// Stage ops are anchored to statement start (^\s*) to avoid false positives
|
|
62
|
-
// on common words in data values (e.g., WHERE name = 'Get Ready').
|
|
63
|
-
// MERGE/SHOW/DESCRIBE/EXPLAIN/USE use word-boundary since they rarely
|
|
64
|
-
// appear as data values.
|
|
65
|
-
const SNOWFLAKE_FORBIDDEN_PATTERNS = [
|
|
66
|
-
/^\s*(PUT|GET|LIST|REMOVE|RM)\b/i,
|
|
67
|
-
/\b(MERGE)\b/i,
|
|
68
|
-
/\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
// DuckDB-specific patterns — block PRAGMA, ATTACH, DETACH, INSTALL,
|
|
72
|
-
// EXPORT, IMPORT, CHECKPOINT, file-reading functions, and SET.
|
|
73
|
-
// Note: LOAD is already blocked by base FORBIDDEN_PATTERNS.
|
|
74
|
-
const DUCKDB_FORBIDDEN_PATTERNS = [
|
|
75
|
-
/\b(PRAGMA)\b/i,
|
|
76
|
-
/\b(ATTACH|DETACH)\b/i,
|
|
77
|
-
/\b(INSTALL)\b/i,
|
|
78
|
-
/\b(EXPORT|IMPORT)\b/i,
|
|
79
|
-
/\b(CHECKPOINT)\b/i,
|
|
80
|
-
/\b(DESCRIBE|EXPLAIN|SHOW)\b/i,
|
|
81
|
-
// Block file-reading table functions that can access the host filesystem
|
|
82
|
-
/\b(read_csv_auto|read_csv|read_parquet|read_json|read_json_auto|read_text)\b/i,
|
|
83
|
-
/\b(parquet_scan|csv_scan|json_scan)\b/i,
|
|
84
|
-
// Block SET for configuration variables (DuckDB has no session-level read-only guard for :memory:)
|
|
85
|
-
/^\s*SET\b/i,
|
|
86
|
-
];
|
|
87
|
-
|
|
88
68
|
/**
|
|
89
69
|
* Map DBType to node-sql-parser dialect string.
|
|
90
|
-
*
|
|
91
|
-
*
|
|
70
|
+
*
|
|
71
|
+
* When a connectionId is provided, plugin-registered metadata is checked first.
|
|
72
|
+
* Falls back to the hardcoded switch for known types, and defaults to
|
|
73
|
+
* "PostgresQL" for unknown/custom dbType strings (plugin escape hatch).
|
|
92
74
|
*/
|
|
93
|
-
export function parserDatabase(dbType: DBType): string {
|
|
75
|
+
export function parserDatabase(dbType: DBType | string, connectionId?: string): string {
|
|
76
|
+
// 1. Plugin metadata takes precedence
|
|
77
|
+
if (connectionId) {
|
|
78
|
+
const pluginDialect = connections.getParserDialect(connectionId);
|
|
79
|
+
if (pluginDialect) return pluginDialect;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Core types + fallback for plugin-registered types
|
|
94
83
|
switch (dbType) {
|
|
95
84
|
case "postgres": return "PostgresQL";
|
|
96
85
|
case "mysql": return "MySQL";
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"Salesforce uses SOQL, not SQL. Use the querySalesforce tool instead of executeSQL.",
|
|
86
|
+
default:
|
|
87
|
+
// Unknown types (plugin-registered via `string & {}`) default to
|
|
88
|
+
// PostgreSQL mode as a safe fallback. Log a warning so plugin authors
|
|
89
|
+
// know to register a parserDialect via ConnectionPluginMeta.
|
|
90
|
+
log.warn(
|
|
91
|
+
{ dbType, connectionId },
|
|
92
|
+
"No parser dialect registered for dbType '%s' — falling back to PostgreSQL parser. " +
|
|
93
|
+
"Register a parserDialect via ConnectionPluginMeta to use the correct SQL grammar.",
|
|
94
|
+
dbType,
|
|
107
95
|
);
|
|
108
|
-
|
|
109
|
-
const _exhaustive: never = dbType;
|
|
110
|
-
throw new Error(`Unknown database type: ${_exhaustive}`);
|
|
111
|
-
}
|
|
96
|
+
return "PostgresQL";
|
|
112
97
|
}
|
|
113
98
|
}
|
|
114
99
|
|
|
115
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Get extra forbidden patterns for a connection.
|
|
102
|
+
*
|
|
103
|
+
* When a connectionId is provided, plugin-registered patterns are checked first.
|
|
104
|
+
* Falls back to the hardcoded arrays for known types, and returns an empty
|
|
105
|
+
* array for unknown/custom dbType strings.
|
|
106
|
+
*/
|
|
107
|
+
function getExtraPatterns(dbType: DBType | string, connectionId?: string): RegExp[] {
|
|
108
|
+
// 1. Plugin metadata takes precedence
|
|
109
|
+
if (connectionId) {
|
|
110
|
+
const pluginPatterns = connections.getForbiddenPatterns(connectionId);
|
|
111
|
+
if (pluginPatterns.length > 0) return pluginPatterns;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 2. Core types + fallback for plugin-registered types
|
|
116
115
|
switch (dbType) {
|
|
117
116
|
case "postgres": return [];
|
|
118
117
|
case "mysql": return MYSQL_FORBIDDEN_PATTERNS;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
default:
|
|
119
|
+
// Unknown types (plugin-registered) — no extra patterns from core.
|
|
120
|
+
// Warn so plugin authors know to register forbiddenPatterns.
|
|
121
|
+
if (dbType) {
|
|
122
|
+
log.warn(
|
|
123
|
+
{ dbType, connectionId },
|
|
124
|
+
"No forbidden patterns registered for dbType '%s' — only base DML/DDL patterns apply. " +
|
|
125
|
+
"Register forbiddenPatterns via ConnectionPluginMeta for database-specific protection.",
|
|
126
|
+
dbType,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return [];
|
|
124
130
|
}
|
|
125
131
|
}
|
|
126
132
|
|
|
@@ -128,29 +134,23 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
|
|
|
128
134
|
// Resolve DB type for this connection.
|
|
129
135
|
// When an explicit connectionId is given but not found, return a validation
|
|
130
136
|
// error instead of silently falling back — wrong parser mode is a security risk.
|
|
131
|
-
let dbType: DBType;
|
|
137
|
+
let dbType: DBType | string;
|
|
132
138
|
if (connectionId) {
|
|
133
139
|
try {
|
|
134
140
|
dbType = connections.getDBType(connectionId);
|
|
135
|
-
} catch {
|
|
141
|
+
} catch (err) {
|
|
142
|
+
log.debug({ err, connectionId }, "getDBType failed for connectionId");
|
|
136
143
|
return { valid: false, error: `Connection "${connectionId}" is not registered.` };
|
|
137
144
|
}
|
|
138
145
|
} else {
|
|
139
146
|
try {
|
|
140
147
|
dbType = detectDBType();
|
|
141
|
-
} catch {
|
|
142
|
-
|
|
148
|
+
} catch (err) {
|
|
149
|
+
log.debug({ err }, "detectDBType failed — no valid datasource configured");
|
|
150
|
+
return { valid: false, error: "No valid datasource configured. Set ATLAS_DATASOURCE_URL to a PostgreSQL or MySQL connection string, or register a datasource plugin." };
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
// Salesforce uses SOQL — redirect to the querySalesforce tool
|
|
147
|
-
if (dbType === "salesforce") {
|
|
148
|
-
return {
|
|
149
|
-
valid: false,
|
|
150
|
-
error: "This connection uses Salesforce (SOQL). Use the querySalesforce tool instead of executeSQL.",
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
154
|
// 0. Reject empty / whitespace-only input
|
|
155
155
|
const trimmed = sql.trim().replace(/;\s*$/, "");
|
|
156
156
|
if (!trimmed) {
|
|
@@ -158,10 +158,14 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// 1. Regex guard against mutation keywords
|
|
161
|
-
|
|
161
|
+
//
|
|
162
|
+
// Strip comments before testing so that leading block/line comments
|
|
163
|
+
// cannot bypass start-of-string anchored patterns (e.g. `/* x */ KILL ...`).
|
|
164
|
+
const forRegex = stripSqlComments(trimmed);
|
|
165
|
+
const extraPatterns = getExtraPatterns(dbType, connectionId);
|
|
162
166
|
const patterns = [...FORBIDDEN_PATTERNS, ...extraPatterns];
|
|
163
167
|
for (const pattern of patterns) {
|
|
164
|
-
if (pattern.test(
|
|
168
|
+
if (pattern.test(forRegex)) {
|
|
165
169
|
return {
|
|
166
170
|
valid: false,
|
|
167
171
|
error: `Forbidden SQL operation detected: ${pattern.source}`,
|
|
@@ -177,7 +181,7 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
|
|
|
177
181
|
// attempt. The agent can always reformulate into standard SQL that parses.
|
|
178
182
|
const cteNames = new Set<string>();
|
|
179
183
|
try {
|
|
180
|
-
const ast = parser.astify(trimmed, { database: parserDatabase(dbType) });
|
|
184
|
+
const ast = parser.astify(trimmed, { database: parserDatabase(dbType, connectionId) });
|
|
181
185
|
const statements = Array.isArray(ast) ? ast : [ast];
|
|
182
186
|
|
|
183
187
|
// Single-statement check — reject batched queries
|
|
@@ -192,7 +196,8 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
|
|
|
192
196
|
error: `Only SELECT statements are allowed, got: ${stmt.type}`,
|
|
193
197
|
};
|
|
194
198
|
}
|
|
195
|
-
//
|
|
199
|
+
// CTE extraction: node-sql-parser doesn't expose .with in its type definitions,
|
|
200
|
+
// but the property exists at runtime for SELECT statements with CTEs.
|
|
196
201
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
197
202
|
if (Array.isArray((stmt as any).with)) {
|
|
198
203
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -204,21 +209,16 @@ export function validateSQL(sql: string, connectionId?: string): { valid: boolea
|
|
|
204
209
|
}
|
|
205
210
|
} catch (err) {
|
|
206
211
|
const detail = err instanceof Error ? err.message : "";
|
|
207
|
-
const hint = dbType === "clickhouse"
|
|
208
|
-
? " Note: ClickHouse-specific syntax (PREWHERE, LIMIT BY, arrayJoin) is not supported by the SQL validator. Use standard SQL equivalents."
|
|
209
|
-
: dbType === "duckdb"
|
|
210
|
-
? " Note: DuckDB-specific syntax (QUALIFY, EXCLUDE, REPLACE, STRUCT literals) may not be supported by the SQL validator. Use standard SQL equivalents."
|
|
211
|
-
: "";
|
|
212
212
|
return {
|
|
213
213
|
valid: false,
|
|
214
|
-
error: `Query could not be parsed.${detail ? ` ${detail}.` : ""} Rewrite using standard SQL syntax
|
|
214
|
+
error: `Query could not be parsed.${detail ? ` ${detail}.` : ""} Rewrite using standard SQL syntax.`,
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// 3. Table whitelist check
|
|
219
219
|
if (process.env.ATLAS_TABLE_WHITELIST !== "false") {
|
|
220
220
|
try {
|
|
221
|
-
const tables = parser.tableList(trimmed, { database: parserDatabase(dbType) });
|
|
221
|
+
const tables = parser.tableList(trimmed, { database: parserDatabase(dbType, connectionId) });
|
|
222
222
|
const allowed = getWhitelistedTables(connectionId);
|
|
223
223
|
|
|
224
224
|
for (const ref of tables) {
|
|
@@ -515,7 +515,7 @@ Rules:
|
|
|
515
515
|
// Extract tables from the (possibly plugin-mutated) SQL
|
|
516
516
|
let queriedTables: Set<string>;
|
|
517
517
|
try {
|
|
518
|
-
const dialect = parserDatabase(dbType);
|
|
518
|
+
const dialect = parserDatabase(dbType, connId);
|
|
519
519
|
const tableRefs = parser.tableList(normalizedMutated, { database: dialect });
|
|
520
520
|
queriedTables = new Set(
|
|
521
521
|
tableRefs
|
|
@@ -39,6 +39,12 @@ declare module "@vercel/sandbox" {
|
|
|
39
39
|
stderr(): Promise<string>;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** Network policy update — replaces the current firewall configuration. */
|
|
43
|
+
type NetworkPolicyUpdate =
|
|
44
|
+
| "deny-all"
|
|
45
|
+
| "allow-all"
|
|
46
|
+
| { allow?: string[] | Record<string, unknown>; subnets?: { allow?: string[]; deny?: string[] } };
|
|
47
|
+
|
|
42
48
|
class Sandbox {
|
|
43
49
|
static create(opts?: SandboxCreateOptions): Promise<Sandbox>;
|
|
44
50
|
mkDir(path: string): Promise<void>;
|
|
@@ -49,6 +55,7 @@ declare module "@vercel/sandbox" {
|
|
|
49
55
|
args?: string[],
|
|
50
56
|
opts?: { signal?: AbortSignal }
|
|
51
57
|
): Promise<CommandFinished>;
|
|
58
|
+
updateNetworkPolicy(policy: NetworkPolicyUpdate): Promise<void>;
|
|
52
59
|
stop(): Promise<Sandbox>;
|
|
53
60
|
}
|
|
54
61
|
}
|
|
@@ -1,26 +1,95 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
3
4
|
import type { ReactNode } from "react";
|
|
4
5
|
import { SidebarProvider, SidebarInset, SidebarTrigger } from "@/components/ui/sidebar";
|
|
5
6
|
import { Separator } from "@/components/ui/separator";
|
|
6
7
|
import { AdminSidebar } from "./admin-sidebar";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { useAtlasConfig } from "@/ui/context";
|
|
9
|
+
import { ManagedAuthCard } from "@/ui/components/chat/managed-auth-card";
|
|
10
|
+
import { LoadingState } from "./loading-state";
|
|
11
|
+
import { ChangePasswordDialog } from "./change-password-dialog";
|
|
11
12
|
|
|
12
13
|
export function AdminLayout({ children }: { children: ReactNode }) {
|
|
14
|
+
const { authClient, apiUrl, isCrossOrigin } = useAtlasConfig();
|
|
15
|
+
const session = authClient.useSession();
|
|
16
|
+
const [passwordChangeRequired, setPasswordChangeRequired] = useState(false);
|
|
17
|
+
|
|
18
|
+
const credentials: RequestCredentials = isCrossOrigin ? "include" : "same-origin";
|
|
19
|
+
|
|
20
|
+
// Check if password change is required after session loads
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!session.data?.user) return;
|
|
23
|
+
|
|
24
|
+
async function checkPasswordStatus() {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`${apiUrl}/api/v1/admin/me/password-status`, { credentials });
|
|
27
|
+
if (!res.ok) return;
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
if (data.passwordChangeRequired) setPasswordChangeRequired(true);
|
|
30
|
+
} catch {
|
|
31
|
+
// Non-critical — skip silently
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
checkPasswordStatus();
|
|
35
|
+
}, [session.data?.user, apiUrl, credentials]);
|
|
36
|
+
|
|
37
|
+
// Loading session
|
|
38
|
+
if (session.isPending) {
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex h-dvh items-center justify-center">
|
|
41
|
+
<LoadingState message="Checking authentication..." />
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Not signed in
|
|
47
|
+
if (!session.data?.user) {
|
|
48
|
+
return <ManagedAuthCard />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Signed in but not admin
|
|
52
|
+
const role = (session.data.user as Record<string, unknown>).role;
|
|
53
|
+
if (role !== "admin") {
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex h-dvh items-center justify-center">
|
|
56
|
+
<div className="w-full max-w-sm space-y-3 rounded-lg border border-zinc-200 bg-zinc-50 p-6 text-center dark:border-zinc-700 dark:bg-zinc-900">
|
|
57
|
+
<h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
|
58
|
+
Access Denied
|
|
59
|
+
</h2>
|
|
60
|
+
<p className="text-sm text-zinc-500 dark:text-zinc-400">
|
|
61
|
+
The admin console requires the <strong>admin</strong> role. You are signed in
|
|
62
|
+
as <strong>{session.data.user.email}</strong> with role <strong>{String(role ?? "viewer")}</strong>.
|
|
63
|
+
</p>
|
|
64
|
+
<button
|
|
65
|
+
onClick={() => authClient.signOut()}
|
|
66
|
+
className="mt-2 rounded-lg bg-zinc-200 px-4 py-2 text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-300 dark:bg-zinc-700 dark:text-zinc-100 dark:hover:bg-zinc-600"
|
|
67
|
+
>
|
|
68
|
+
Sign out
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
13
75
|
return (
|
|
14
76
|
<SidebarProvider>
|
|
15
77
|
<AdminSidebar />
|
|
16
78
|
<SidebarInset>
|
|
17
|
-
<header className="flex h-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
79
|
+
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
|
80
|
+
<div className="flex items-center gap-2 px-4">
|
|
81
|
+
<SidebarTrigger className="-ml-1" />
|
|
82
|
+
<Separator orientation="vertical" className="mr-2 h-4" />
|
|
83
|
+
<span className="text-sm font-medium text-muted-foreground">Admin Console</span>
|
|
84
|
+
</div>
|
|
21
85
|
</header>
|
|
22
86
|
<div className="flex-1 overflow-auto">{children}</div>
|
|
23
87
|
</SidebarInset>
|
|
88
|
+
|
|
89
|
+
<ChangePasswordDialog
|
|
90
|
+
open={passwordChangeRequired}
|
|
91
|
+
onComplete={() => setPasswordChangeRequired(false)}
|
|
92
|
+
/>
|
|
24
93
|
</SidebarProvider>
|
|
25
94
|
);
|
|
26
95
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Database,
|
|
8
8
|
Cable,
|
|
9
9
|
ScrollText,
|
|
10
|
+
Users,
|
|
10
11
|
Puzzle,
|
|
11
12
|
CalendarClock,
|
|
12
13
|
Zap,
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
SidebarMenuItem,
|
|
24
25
|
SidebarMenuButton,
|
|
25
26
|
SidebarFooter,
|
|
26
|
-
|
|
27
|
+
SidebarRail,
|
|
27
28
|
} from "@/components/ui/sidebar";
|
|
28
29
|
|
|
29
30
|
const navItems = [
|
|
@@ -31,6 +32,7 @@ const navItems = [
|
|
|
31
32
|
{ href: "/admin/semantic", label: "Semantic Layer", icon: Database },
|
|
32
33
|
{ href: "/admin/connections", label: "Connections", icon: Cable },
|
|
33
34
|
{ href: "/admin/audit", label: "Audit", icon: ScrollText },
|
|
35
|
+
{ href: "/admin/users", label: "Users", icon: Users },
|
|
34
36
|
{ href: "/admin/plugins", label: "Plugins", icon: Puzzle },
|
|
35
37
|
{ href: "/admin/scheduled-tasks", label: "Scheduled Tasks", icon: CalendarClock },
|
|
36
38
|
{ href: "/admin/actions", label: "Actions", icon: Zap },
|
|
@@ -45,20 +47,26 @@ export function AdminSidebar() {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
return (
|
|
48
|
-
<Sidebar>
|
|
49
|
-
<SidebarHeader
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
<Sidebar collapsible="icon">
|
|
51
|
+
<SidebarHeader>
|
|
52
|
+
<SidebarMenu>
|
|
53
|
+
<SidebarMenuItem>
|
|
54
|
+
<SidebarMenuButton size="lg" asChild>
|
|
55
|
+
<Link href="/admin">
|
|
56
|
+
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
|
57
|
+
<svg viewBox="0 0 256 256" fill="none" className="size-4" aria-hidden="true">
|
|
58
|
+
<path d="M128 24 L232 208 L24 208 Z" stroke="currentColor" strokeWidth="20" fill="none" strokeLinejoin="round" />
|
|
59
|
+
</svg>
|
|
60
|
+
</div>
|
|
61
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
62
|
+
<span className="truncate font-semibold">Atlas</span>
|
|
63
|
+
<span className="truncate text-xs">Admin Console</span>
|
|
64
|
+
</div>
|
|
65
|
+
</Link>
|
|
66
|
+
</SidebarMenuButton>
|
|
67
|
+
</SidebarMenuItem>
|
|
68
|
+
</SidebarMenu>
|
|
60
69
|
</SidebarHeader>
|
|
61
|
-
<SidebarSeparator />
|
|
62
70
|
<SidebarContent>
|
|
63
71
|
<SidebarGroup>
|
|
64
72
|
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
|
@@ -68,7 +76,7 @@ export function AdminSidebar() {
|
|
|
68
76
|
<SidebarMenuItem key={item.href}>
|
|
69
77
|
<SidebarMenuButton asChild isActive={isActive(item)} tooltip={item.label}>
|
|
70
78
|
<Link href={item.href}>
|
|
71
|
-
<item.icon
|
|
79
|
+
<item.icon />
|
|
72
80
|
<span>{item.label}</span>
|
|
73
81
|
</Link>
|
|
74
82
|
</SidebarMenuButton>
|
|
@@ -79,18 +87,18 @@ export function AdminSidebar() {
|
|
|
79
87
|
</SidebarGroup>
|
|
80
88
|
</SidebarContent>
|
|
81
89
|
<SidebarFooter>
|
|
82
|
-
<SidebarSeparator />
|
|
83
90
|
<SidebarMenu>
|
|
84
91
|
<SidebarMenuItem>
|
|
85
92
|
<SidebarMenuButton asChild tooltip="Back to Chat">
|
|
86
93
|
<Link href="/">
|
|
87
|
-
<ArrowLeft
|
|
94
|
+
<ArrowLeft />
|
|
88
95
|
<span>Back to Chat</span>
|
|
89
96
|
</Link>
|
|
90
97
|
</SidebarMenuButton>
|
|
91
98
|
</SidebarMenuItem>
|
|
92
99
|
</SidebarMenu>
|
|
93
100
|
</SidebarFooter>
|
|
101
|
+
<SidebarRail />
|
|
94
102
|
</Sidebar>
|
|
95
103
|
);
|
|
96
104
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogContent,
|
|
7
|
+
AlertDialogDescription,
|
|
8
|
+
AlertDialogFooter,
|
|
9
|
+
AlertDialogHeader,
|
|
10
|
+
AlertDialogTitle,
|
|
11
|
+
} from "@/components/ui/alert-dialog";
|
|
12
|
+
import { Button } from "@/components/ui/button";
|
|
13
|
+
import { Input } from "@/components/ui/input";
|
|
14
|
+
import { useAtlasConfig } from "@/ui/context";
|
|
15
|
+
|
|
16
|
+
export function ChangePasswordDialog({
|
|
17
|
+
open,
|
|
18
|
+
onComplete,
|
|
19
|
+
}: {
|
|
20
|
+
open: boolean;
|
|
21
|
+
onComplete: () => void;
|
|
22
|
+
}) {
|
|
23
|
+
const { apiUrl, isCrossOrigin } = useAtlasConfig();
|
|
24
|
+
const credentials: RequestCredentials = isCrossOrigin ? "include" : "same-origin";
|
|
25
|
+
|
|
26
|
+
const [currentPassword, setCurrentPassword] = useState("atlas-dev");
|
|
27
|
+
const [newPassword, setNewPassword] = useState("");
|
|
28
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
29
|
+
const [error, setError] = useState("");
|
|
30
|
+
const [loading, setLoading] = useState(false);
|
|
31
|
+
|
|
32
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
setError("");
|
|
35
|
+
|
|
36
|
+
if (newPassword.length < 8) {
|
|
37
|
+
setError("Password must be at least 8 characters.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (newPassword !== confirmPassword) {
|
|
41
|
+
setError("Passwords do not match.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (newPassword === currentPassword) {
|
|
45
|
+
setError("New password must be different from current password.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setLoading(true);
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch(`${apiUrl}/api/v1/admin/me/password`, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
credentials,
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({ currentPassword, newPassword }),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const data = await res.json().catch(() => ({}));
|
|
60
|
+
setError(data.message ?? `Failed (HTTP ${res.status})`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onComplete();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
setError(err instanceof Error ? err.message : "Failed to change password");
|
|
67
|
+
} finally {
|
|
68
|
+
setLoading(false);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<AlertDialog open={open}>
|
|
74
|
+
<AlertDialogContent className="sm:max-w-md" onEscapeKeyDown={(e) => e.preventDefault()}>
|
|
75
|
+
<form onSubmit={handleSubmit}>
|
|
76
|
+
<AlertDialogHeader>
|
|
77
|
+
<AlertDialogTitle>Change your password</AlertDialogTitle>
|
|
78
|
+
<AlertDialogDescription>
|
|
79
|
+
You're using the default dev password. Please set a new password to continue.
|
|
80
|
+
</AlertDialogDescription>
|
|
81
|
+
</AlertDialogHeader>
|
|
82
|
+
|
|
83
|
+
<div className="space-y-3 py-4">
|
|
84
|
+
<div className="space-y-1">
|
|
85
|
+
<label className="text-xs font-medium text-muted-foreground">Current password</label>
|
|
86
|
+
<Input
|
|
87
|
+
type="password"
|
|
88
|
+
value={currentPassword}
|
|
89
|
+
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
90
|
+
required
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="space-y-1">
|
|
94
|
+
<label className="text-xs font-medium text-muted-foreground">New password</label>
|
|
95
|
+
<Input
|
|
96
|
+
type="password"
|
|
97
|
+
value={newPassword}
|
|
98
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
99
|
+
required
|
|
100
|
+
minLength={8}
|
|
101
|
+
placeholder="At least 8 characters"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
<div className="space-y-1">
|
|
105
|
+
<label className="text-xs font-medium text-muted-foreground">Confirm new password</label>
|
|
106
|
+
<Input
|
|
107
|
+
type="password"
|
|
108
|
+
value={confirmPassword}
|
|
109
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
110
|
+
required
|
|
111
|
+
minLength={8}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
{error && (
|
|
115
|
+
<p className="text-xs text-red-600 dark:text-red-400">{error}</p>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<AlertDialogFooter>
|
|
120
|
+
<Button type="submit" disabled={loading}>
|
|
121
|
+
{loading ? "Changing..." : "Change password"}
|
|
122
|
+
</Button>
|
|
123
|
+
</AlertDialogFooter>
|
|
124
|
+
</form>
|
|
125
|
+
</AlertDialogContent>
|
|
126
|
+
</AlertDialog>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -62,7 +62,7 @@ function normalizeList<T>(
|
|
|
62
62
|
keyName: string,
|
|
63
63
|
): (T & Record<string, unknown>)[] {
|
|
64
64
|
if (!data) return [];
|
|
65
|
-
if (Array.isArray(data)) return data;
|
|
65
|
+
if (Array.isArray(data)) return data as (T & Record<string, unknown>)[];
|
|
66
66
|
return Object.entries(data).map(([key, value]) => ({ ...value, [keyName]: key }));
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -211,8 +211,8 @@ export function EntityDetail({ entity }: { entity: EntityData }) {
|
|
|
211
211
|
<section>
|
|
212
212
|
<h3 className="mb-3 text-sm font-semibold">Query Patterns ({patterns.length})</h3>
|
|
213
213
|
<div className="space-y-3">
|
|
214
|
-
{patterns.map((p) => (
|
|
215
|
-
<Card key={p.name} className="shadow-none">
|
|
214
|
+
{patterns.map((p, i) => (
|
|
215
|
+
<Card key={`${p.name}-${i}`} className="shadow-none">
|
|
216
216
|
<CardHeader className="py-3 pb-2">
|
|
217
217
|
<CardTitle className="text-sm">{p.name}</CardTitle>
|
|
218
218
|
<p className="text-xs text-muted-foreground">{p.description}</p>
|