From 23b13be9f68b27556f2d5303accd787768c04e90 Mon Sep 17 00:00:00 2001 From: Alexander Marshalov Date: Wed, 3 Dec 2025 16:23:55 +0100 Subject: [PATCH] =?UTF-8?q?refactoring=20of=20UI:=20-=20The=20interface=20?= =?UTF-8?q?has=20been=20made=20more=20compact=20=E2=80=94=20smaller=20font?= =?UTF-8?q?s=20and=20less=20space=20-=20Added=20time=20range=20for=20query?= =?UTF-8?q?ing=20-=20The=20option=20to=20choose=20between=20Query=20data?= =?UTF-8?q?=20and=20Translate=20only=20has=20been=20implemented=20-=20Impr?= =?UTF-8?q?oved=20autocomplete=20for=20SQL=20-=20Translated=20LogsQL=20tex?= =?UTF-8?q?t=20fixed=20at=20the=20top=20of=20table=20-=20A=20lot=20of=20sm?= =?UTF-8?q?all=20style=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lint.yaml | 2 +- .github/workflows/release.yml | 2 +- cmd/sql-to-logsql/api/server.go | 21 +- cmd/sql-to-logsql/api/server_test.go | 12 +- cmd/sql-to-logsql/web/ui/package-lock.json | 326 +++++++++++++++++- cmd/sql-to-logsql/web/ui/package.json | 11 +- .../src/components/date-picker/DatePicker.tsx | 148 ++++++++ .../ui/src/components/date-picker/index.ts | 1 + .../date-time-range/DateTimeRange.tsx | 100 ++++++ .../src/components/date-time-range/index.ts | 1 + .../date-time-range/quick-ranges.ts | 118 +++++++ .../src/components/date-time-range/utils.ts | 20 ++ .../web/ui/src/components/docs/Docs.tsx | 292 ++++++++++------ .../components/logs-endpoint/LogsEndpoint.tsx | 111 +++--- .../components/query-results/QueryResults.tsx | 33 +- .../src/components/sql-editor/SQLEditor.tsx | 121 +++++-- .../src/components/sql-editor/complections.ts | 23 ++ .../web/ui/src/components/ui/button.tsx | 2 + .../web/ui/src/components/ui/calendar.tsx | 214 ++++++++++++ .../web/ui/src/components/ui/command.tsx | 184 ++++++++++ .../web/ui/src/components/ui/dialog.tsx | 141 ++++++++ .../web/ui/src/components/ui/popover.tsx | 46 +++ .../web/ui/src/components/ui/toggle-group.tsx | 83 +++++ .../web/ui/src/components/ui/toggle.tsx | 45 +++ cmd/sql-to-logsql/web/ui/src/index.css | 4 +- .../web/ui/src/pages/main/Main.tsx | 152 ++++---- go.mod | 2 +- lib/vlogs/api.go | 71 ++-- 28 files changed, 1982 insertions(+), 304 deletions(-) create mode 100644 cmd/sql-to-logsql/web/ui/src/components/date-picker/DatePicker.tsx create mode 100644 cmd/sql-to-logsql/web/ui/src/components/date-picker/index.ts create mode 100644 cmd/sql-to-logsql/web/ui/src/components/date-time-range/DateTimeRange.tsx create mode 100644 cmd/sql-to-logsql/web/ui/src/components/date-time-range/index.ts create mode 100644 cmd/sql-to-logsql/web/ui/src/components/date-time-range/quick-ranges.ts create mode 100644 cmd/sql-to-logsql/web/ui/src/components/date-time-range/utils.ts create mode 100644 cmd/sql-to-logsql/web/ui/src/components/ui/calendar.tsx create mode 100644 cmd/sql-to-logsql/web/ui/src/components/ui/command.tsx create mode 100644 cmd/sql-to-logsql/web/ui/src/components/ui/dialog.tsx create mode 100644 cmd/sql-to-logsql/web/ui/src/components/ui/popover.tsx create mode 100644 cmd/sql-to-logsql/web/ui/src/components/ui/toggle-group.tsx create mode 100644 cmd/sql-to-logsql/web/ui/src/components/ui/toggle.tsx diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d7e671c..1d2d74b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.25.3" + go-version: "1.25.5" - name: Run ui build run: make ui-build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a4476e..b6c09a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v6 with: - go-version: 1.25.3 + go-version: 1.25.5 - name: Setup Node.js uses: actions/setup-node@v6 diff --git a/cmd/sql-to-logsql/api/server.go b/cmd/sql-to-logsql/api/server.go index 238d6e2..4db097e 100644 --- a/cmd/sql-to-logsql/api/server.go +++ b/cmd/sql-to-logsql/api/server.go @@ -107,6 +107,9 @@ type queryRequest struct { SQL string `json:"sql"` Endpoint string `json:"endpoint,omitempty"` BearerToken string `json:"bearerToken,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + ExecMode string `json:"execMode,omitempty"` } type queryResponse struct { @@ -135,6 +138,13 @@ func (s *Server) handleQuery(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusBadRequest, queryResponse{Error: "sql query is required"}) return } + execMode := strings.TrimSpace(strings.ToLower(req.ExecMode)) + if execMode != "" && execMode != "translate" && execMode != "query" { + writeJSON(w, http.StatusBadRequest, queryResponse{Error: "invalid execMode: possible values are translate and query"}) + return + } + start := strings.TrimSpace(req.Start) + end := strings.TrimSpace(req.End) statement, err := processQuery(sqlText, s.sp) if err != nil { @@ -158,9 +168,14 @@ func (s *Server) handleQuery(w http.ResponseWriter, r *http.Request) { } resp := queryResponse{LogsQL: statement.LogsQL} - data, err := s.api.Execute(r.Context(), statement, vlogs.EndpointConfig{ - Endpoint: req.Endpoint, - BearerToken: req.BearerToken, + data, err := s.api.Execute(r.Context(), statement, vlogs.RequestParams{ + EndpointConfig: vlogs.EndpointConfig{ + Endpoint: req.Endpoint, + BearerToken: req.BearerToken, + }, + Start: start, + End: end, + ExecMode: execMode, }) if err != nil { log.Printf("ERROR: query execution failed: %v", err) diff --git a/cmd/sql-to-logsql/api/server_test.go b/cmd/sql-to-logsql/api/server_test.go index 38acc27..5d478e4 100644 --- a/cmd/sql-to-logsql/api/server_test.go +++ b/cmd/sql-to-logsql/api/server_test.go @@ -31,6 +31,12 @@ func TestHandleQuerySuccess(t *testing.T) { if got := req.Form.Get("query"); got != "*" { t.Fatalf("unexpected query sent: %q", got) } + if got := req.Form.Get("start"); got != "1764770090000" { + t.Fatalf("unexpected start sent: %q", got) + } + if got := req.Form.Get("end"); got != "1764856490000" { + t.Fatalf("unexpected end sent: %q", got) + } resp := &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(`{"status":"ok"}`)), @@ -41,7 +47,11 @@ func TestHandleQuerySuccess(t *testing.T) { }), }) - reqBody := map[string]string{"sql": "SELECT * FROM logs"} + reqBody := map[string]string{ + "sql": "SELECT * FROM logs", + "start": "1764770090000", + "end": "1764856490000", + } buf, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPost, "/api/v1/sql-to-logsql", bytes.NewReader(buf)) req.Header.Set("Content-Type", "application/json") diff --git a/cmd/sql-to-logsql/web/ui/package-lock.json b/cmd/sql-to-logsql/web/ui/package-lock.json index bf47e07..75a6b29 100644 --- a/cmd/sql-to-logsql/web/ui/package-lock.json +++ b/cmd/sql-to-logsql/web/ui/package-lock.json @@ -11,21 +11,30 @@ "@monaco-editor/react": "^4.7.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/vite": "^4.1.16", + "chrono-node": "^2.9.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "dayjs": "^1.11.19", "lucide-react": "^0.552.0", "next-themes": "^0.4.6", "prettier": "^3.6.2", "react": "^19.2.0", + "react-day-picker": "^9.11.3", "react-dom": "^19.2.0", "react-resizable-panels": "^3.0.6", "sonner": "^2.0.7", @@ -331,6 +340,12 @@ "node": ">=6.9.0" } }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, "node_modules/@emnapi/core": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", @@ -894,6 +909,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -924,6 +957,60 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1047,6 +1134,61 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -1150,6 +1292,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -1255,6 +1415,24 @@ } } }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", @@ -1279,9 +1457,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1355,6 +1533,60 @@ } } }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", @@ -1389,6 +1621,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -2935,6 +3185,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chrono-node": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.9.0.tgz", + "integrity": "sha512-glI4YY2Jy6JII5l3d5FN6rcrIbKSQqKPhWsIRYPK2IK8Mm4Q1ZZFdYIaDqglUNf7gNwG+kWIzTn0omzzE0VkvQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -2956,6 +3215,22 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3012,6 +3287,28 @@ "devOptional": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4249,6 +4546,27 @@ "node": ">=0.10.0" } }, + "node_modules/react-day-picker": { + "version": "9.11.3", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.3.tgz", + "integrity": "sha512-7lD12UvGbkyXqgzbYIGQTbl+x29B9bAf+k0pP5Dcs1evfpKk6zv4EdH/edNc8NxcmCiTNXr2HIYPrSZ3XvmVBg==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", diff --git a/cmd/sql-to-logsql/web/ui/package.json b/cmd/sql-to-logsql/web/ui/package.json index ec180da..67cf4f5 100644 --- a/cmd/sql-to-logsql/web/ui/package.json +++ b/cmd/sql-to-logsql/web/ui/package.json @@ -13,21 +13,30 @@ "@monaco-editor/react": "^4.7.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/vite": "^4.1.16", + "chrono-node": "^2.9.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "dayjs": "^1.11.19", "lucide-react": "^0.552.0", "next-themes": "^0.4.6", "prettier": "^3.6.2", "react": "^19.2.0", + "react-day-picker": "^9.11.3", "react-dom": "^19.2.0", "react-resizable-panels": "^3.0.6", "sonner": "^2.0.7", diff --git a/cmd/sql-to-logsql/web/ui/src/components/date-picker/DatePicker.tsx b/cmd/sql-to-logsql/web/ui/src/components/date-picker/DatePicker.tsx new file mode 100644 index 0000000..33fc58a --- /dev/null +++ b/cmd/sql-to-logsql/web/ui/src/components/date-picker/DatePicker.tsx @@ -0,0 +1,148 @@ +import { Label } from "@/components/ui/label.tsx"; +import { Input } from "@/components/ui/input.tsx"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover.tsx"; +import { Button } from "@/components/ui/button.tsx"; +import { CalendarIcon } from "lucide-react"; +import { Calendar } from "@/components/ui/calendar.tsx"; +import { useId, useState } from "react"; +import { parseDate } from "chrono-node"; +import { cn } from "@/lib/utils"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip.tsx"; +import dayjs from "dayjs"; + +export interface DatePickerProps { + readonly label?: string; + readonly value?: string; + readonly onValueChange?: (value: string) => void; + readonly disabled?: boolean; + readonly tooltip?: string; + readonly error?: string; + readonly id?: string; +} + +export function DatePicker({ + label, + value, + onValueChange, + disabled, + tooltip, + error: extError, + id: extId, +}: DatePickerProps) { + const [open, setOpen] = useState(false); + const date = (value && parseDate(value)) || undefined; + const [month, setMonth] = useState(date); + const intError = value && (date ? undefined : "invalid date format"); + const error = intError || extError; + const formattedDate = date && formatDate(date); + const intId = useId(); + const id = extId || intId; + + return ( +
+ +
+ + + onValueChange && onValueChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === "ArrowDown") { + e.preventDefault(); + setOpen(true); + } + }} + /> + + {tooltip && ( + + {tooltip} + + )} + + + + + + + { + if (onValueChange) { + onValueChange(formatDate(date)); + } + setOpen(false); + }} + /> + + +
+
+ {error || + (formattedDate && formattedDate === value + ? formattedDate + : formattedDate || "select a date")} +
+
+ ); +} + +function formatDate(date: Date | undefined) { + if (!date) { + return ""; + } + return dayjs(date).format("YYYY-MM-DD HH:mm:ss"); +} diff --git a/cmd/sql-to-logsql/web/ui/src/components/date-picker/index.ts b/cmd/sql-to-logsql/web/ui/src/components/date-picker/index.ts new file mode 100644 index 0000000..8944a15 --- /dev/null +++ b/cmd/sql-to-logsql/web/ui/src/components/date-picker/index.ts @@ -0,0 +1 @@ +export * from "./DatePicker.tsx"; diff --git a/cmd/sql-to-logsql/web/ui/src/components/date-time-range/DateTimeRange.tsx b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/DateTimeRange.tsx new file mode 100644 index 0000000..71d5cad --- /dev/null +++ b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/DateTimeRange.tsx @@ -0,0 +1,100 @@ +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover.tsx"; +import { Button } from "@/components/ui/button.tsx"; +import { CalendarIcon } from "lucide-react"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command.tsx"; +import { DatePicker } from "@/components/date-picker"; +import { useState } from "react"; +import { getCaption } from "@/components/date-time-range/utils.ts"; +import { predefinedQuickRanges } from "@/components/date-time-range/quick-ranges.ts"; + +export interface DateTimeRangeValue { + readonly from: string; + readonly to: string; +} + +export interface DateRangeProps { + readonly value?: DateTimeRangeValue; + readonly onValueChange?: (value: DateTimeRangeValue) => void; +} + +export function DateTimeRange({ value, onValueChange }: DateRangeProps) { + const { from, to } = value ?? { from: "", to: "" }; + const [intFrom, setIntFrom] = useState(from); + const [intTo, setIntTo] = useState(to); + const [open, setOpen] = useState(false); + const onChange = (from: string, to: string) => { + setIntFrom(from); + setIntTo(to); + if (onValueChange) { + onValueChange({ from, to }); + } + setOpen(false); + }; + return ( + + + + + + + + + No date ranges found. + + {predefinedQuickRanges.map((range) => ( + onChange(range.from, range.to)} + > + {range.label} + + ))} + + + +
+
+

Time range

+ + +
+ +
+
+
+ ); +} diff --git a/cmd/sql-to-logsql/web/ui/src/components/date-time-range/index.ts b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/index.ts new file mode 100644 index 0000000..ce98e6c --- /dev/null +++ b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/index.ts @@ -0,0 +1 @@ +export * from "./DateTimeRange.tsx"; diff --git a/cmd/sql-to-logsql/web/ui/src/components/date-time-range/quick-ranges.ts b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/quick-ranges.ts new file mode 100644 index 0000000..31ebe14 --- /dev/null +++ b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/quick-ranges.ts @@ -0,0 +1,118 @@ +export interface QuickRange { + readonly label: string; + readonly from: string; + readonly to: string; +} + +export const predefinedQuickRanges: QuickRange[] = [ + { + label: "Last 5 minutes", + from: "5m ago", + to: "now", + }, + { + label: "Last 10 minutes", + from: "10m ago", + to: "now", + }, + { + label: "Last 15 minutes", + from: "15m ago", + to: "now", + }, + { + label: "Last 20 minutes", + from: "20m ago", + to: "now", + }, + { + label: "Last 30 minutes", + from: "30m ago", + to: "now", + }, + { + label: "Last 1 hour", + from: "1h ago", + to: "now", + }, + { + label: "Last 2 hours", + from: "2h ago", + to: "now", + }, + { + label: "Last 3 hours", + from: "3h ago", + to: "now", + }, + { + label: "Last 6 hour", + from: "6h ago", + to: "now", + }, + { + label: "Last 12 hours", + from: "12h ago", + to: "now", + }, + { + label: "Last 24 hours", + from: "24h ago", + to: "now", + }, + { + label: "Last 2 days", + from: "2d ago", + to: "now", + }, + { + label: "Last 3 days", + from: "3d ago", + to: "now", + }, + { + label: "Last 7 days", + from: "7d ago", + to: "now", + }, + { + label: "Last 14 days", + from: "14d ago", + to: "now", + }, + { + label: "Last 30 days", + from: "30d ago", + to: "now", + }, + { + label: "Last 90 days", + from: "90d ago", + to: "now", + }, + { + label: "Last 6 months", + from: "180d ago", + to: "now", + }, + { + label: "Last 1 year", + from: "1y ago", + to: "now", + }, + { + label: "Last 2 years", + from: "2y ago", + to: "now", + }, + { + label: "Last 3 years", + from: "3y ago", + to: "now", + }, + { + label: "Last 5 years", + from: "5y ago", + to: "now", + }, +]; diff --git a/cmd/sql-to-logsql/web/ui/src/components/date-time-range/utils.ts b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/utils.ts new file mode 100644 index 0000000..59969aa --- /dev/null +++ b/cmd/sql-to-logsql/web/ui/src/components/date-time-range/utils.ts @@ -0,0 +1,20 @@ +import { predefinedQuickRanges } from "@/components/date-time-range/quick-ranges.ts"; + +export function getCaption(from: string, to: string) { + if (from === "" && to === "") { + return "Select a date range"; + } + if (from === "" && to !== "") { + return `To ${to}`; + } + if (from !== "" && to === "") { + return `From ${from}`; + } + const qr = predefinedQuickRanges.find( + (range) => range.from === from && range.to === to, + ); + if (qr) { + return qr.label; + } + return `${from} - ${to}`; +} diff --git a/cmd/sql-to-logsql/web/ui/src/components/docs/Docs.tsx b/cmd/sql-to-logsql/web/ui/src/components/docs/Docs.tsx index 6eed0c8..d1ebbf5 100644 --- a/cmd/sql-to-logsql/web/ui/src/components/docs/Docs.tsx +++ b/cmd/sql-to-logsql/web/ui/src/components/docs/Docs.tsx @@ -1,115 +1,185 @@ -import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card.tsx"; -import {Accordion, AccordionContent, AccordionItem, AccordionTrigger} from "@/components/ui/accordion.tsx"; -import {InfoIcon, LinkIcon} from "lucide-react"; -import {Button} from "@/components/ui/button.tsx"; -import {Separator} from "@/components/ui/separator.tsx"; -import {Badge} from "@/components/ui/badge.tsx"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card.tsx"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion.tsx"; +import { InfoIcon, LinkIcon } from "lucide-react"; +import { Button } from "@/components/ui/button.tsx"; +import { Separator } from "@/components/ui/separator.tsx"; +import { Badge } from "@/components/ui/badge.tsx"; export function Docs() { return ( - - - - Information about SQL to LogsQL - {__APP_VERSION__ == '${VERSION}' ? 'local' : __APP_VERSION__} - - Service that helps to query VictoriaLogs with SQL - - -
- - - -
- - - - - - - Supported statement types - - - -

-

    -
  • SHOW TABLES / VIEWS
  • -
  • DESCRIBE TABLE / VIEW ...
  • -
  • SELECT ... FROM ...
  • -
  • CREATE VIEW ...
  • -
  • DROP VIEW ...
  • -
-

-
-
- - - - - Supported query clauses - - - -

-

    -
  • SELECT, DISTINCT, AS, OVER, PARTITION BY
  • -
  • FROM, WITH, subqueries
  • -
  • WHERE, AND, OR
  • -
  • LEFT JOIN / JOIN / INNER JOIN
  • -
  • LIKE, NOT LIKE, BETWEEN, IN, NOT IN, IS NULL, IS NOT NULL
  • -
  • GROUP BY, HAVING
  • -
  • ORDER BY, ASC, DESC, LIMIT, OFFSET
  • -
  • UNION ALL
  • -
-

-
-
- - - - - Supported functions and operators - - - -

-

    -
  • SUBSTR, CONCAT, LOWER, UPPER, TRIM, LTRIM, RTRIM, REPLACE, REGEXP_REPLACE
  • -
  • CASE/WHEN, LIKE, NOT LIKE, =, !=, <, >, <=, >=, BETWEEN
  • -
  • +,-, *, /, %, ^
  • -
  • ABS, GREATEST, LEAST, ROUND, FLOOR, CEIL, POW, LN, EXP
  • -
  • SUM, COUNT, MAX, MIN, AVG
  • -
  • CURRENT_TIMESTAMP, CURREN_DATE
  • -
  • JSON_VALUE
  • -
-

-
-
- - - - - Supported data sources - - - -

-

    -
  • Only logs table is supported
  • -
  • You can create any views
  • -
-

-
-
-
-
-
- ) + + + + Information about SQL to LogsQL + + {__APP_VERSION__ == "${VERSION}" ? "local" : __APP_VERSION__} + + + + Service that helps to query VictoriaLogs with SQL + + + +
+ + + +
+ + + + + + + Supported statement types + + + +

+

    +
  • + SHOW TABLES / VIEWS +
  • +
  • + DESCRIBE TABLE / VIEW ... +
  • +
  • + SELECT ... FROM ... +
  • +
  • + CREATE VIEW ... +
  • +
  • + DROP VIEW ... +
  • +
+

+
+
+ + + + + Supported query clauses + + + +

+

    +
  • + SELECT, DISTINCT, AS, OVER, PARTITION BY +
  • +
  • + FROM, WITH, subqueries +
  • +
  • + WHERE, AND, OR +
  • +
  • + LEFT JOIN / JOIN / INNER JOIN +
  • +
  • + + LIKE, NOT LIKE, BETWEEN, IN, NOT IN, IS NULL, IS NOT NULL + +
  • +
  • + GROUP BY, HAVING +
  • +
  • + ORDER BY, ASC, DESC, LIMIT, OFFSET +
  • +
  • + UNION ALL +
  • +
+

+
+
+ + + + + Supported functions and operators + + + +

+

    +
  • + + SUBSTR, CONCAT, LOWER, UPPER, TRIM, LTRIM, RTRIM, REPLACE, + REGEXP_REPLACE + +
  • +
  • + + CASE/WHEN, LIKE, NOT LIKE, =, !=, <, >, <=, + >=, BETWEEN + +
  • +
  • + +,-, *, /, %, ^ +
  • +
  • + + ABS, GREATEST, LEAST, ROUND, FLOOR, CEIL, POW, LN, EXP + +
  • +
  • + SUM, COUNT, MAX, MIN, AVG +
  • +
  • + CURRENT_TIMESTAMP, CURREN_DATE +
  • +
  • + JSON_VALUE +
  • +
+

+
+
+ + + + + Supported data sources + + + +

+

    +
  • + Only{" "} + + logs + {" "} + table is supported +
  • +
  • You can create any views
  • +
+

+
+
+
+
+
+ ); } diff --git a/cmd/sql-to-logsql/web/ui/src/components/logs-endpoint/LogsEndpoint.tsx b/cmd/sql-to-logsql/web/ui/src/components/logs-endpoint/LogsEndpoint.tsx index c23bdeb..007a1a1 100644 --- a/cmd/sql-to-logsql/web/ui/src/components/logs-endpoint/LogsEndpoint.tsx +++ b/cmd/sql-to-logsql/web/ui/src/components/logs-endpoint/LogsEndpoint.tsx @@ -1,20 +1,23 @@ import { - Card, CardAction, - CardContent, CardDescription, + Card, + CardAction, + CardContent, + CardDescription, CardHeader, CardTitle, } from "@/components/ui/card.tsx"; import { Input } from "@/components/ui/input.tsx"; import { Label } from "@/components/ui/label.tsx"; -import {Switch} from "@/components/ui/switch.tsx"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group.tsx"; export interface LogsEndpointProps { readonly endpointUrl?: string; readonly bearerToken?: string; + readonly execMode?: "translate" | "query"; readonly onUrlChange?: (url: string) => void; readonly onTokenChange?: (password: string) => void; + readonly onExecModeChange?: (mode: "translate" | "query") => void; readonly isLoading?: boolean; - readonly endpointReadOnly?: boolean; readonly endpointEnabled?: boolean; readonly onEndpointEnabledChange?: (enabled: boolean) => void; } @@ -24,59 +27,67 @@ export function LogsEndpoint({ onUrlChange, bearerToken, onTokenChange, + execMode = "query", + onExecModeChange, isLoading, - endpointReadOnly, endpointEnabled, - onEndpointEnabledChange, }: LogsEndpointProps) { return ( - + VictoriaLogs endpoint - {!endpointReadOnly && - - You can query data from VictoriaLogs instance or just translate SQL to LogsQL without querying - - } - {!endpointReadOnly && - - - } + + You can query data from VictoriaLogs instance or just translate SQL to + LogsQL without querying + + + { + if ( + onExecModeChange && + (value === "translate" || value === "query") + ) { + onExecModeChange(value); + } + }} + variant="outline" + > + + Translate + + + Query + + + - -
- - onUrlChange && onUrlChange(e.target.value)} - /> -
-
- - onTokenChange && onTokenChange(e.target.value)} - /> -
-
+ {execMode !== "translate" && ( + +
+ + onUrlChange && onUrlChange(e.target.value)} + /> +
+
+ + onTokenChange && onTokenChange(e.target.value)} + /> +
+
+ )}
); } diff --git a/cmd/sql-to-logsql/web/ui/src/components/query-results/QueryResults.tsx b/cmd/sql-to-logsql/web/ui/src/components/query-results/QueryResults.tsx index bf54570..102c436 100644 --- a/cmd/sql-to-logsql/web/ui/src/components/query-results/QueryResults.tsx +++ b/cmd/sql-to-logsql/web/ui/src/components/query-results/QueryResults.tsx @@ -1,4 +1,10 @@ -import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "../ui/card"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "../ui/card"; import { QueryResultsTable } from "@/components/query-results/QueryResultsTable.tsx"; import { Skeleton } from "@/components/ui/skeleton.tsx"; @@ -6,10 +12,15 @@ export interface QueryResultsProps { readonly query?: string; readonly data?: unknown; readonly isLoading?: boolean; - readonly endpointEnabled?: boolean; + readonly execMode?: "translate" | "query"; } -export function QueryResults({ query, data, isLoading, endpointEnabled }: QueryResultsProps) { +export function QueryResults({ + query, + data, + isLoading, + execMode, +}: QueryResultsProps) { if (isLoading) { return (
@@ -26,22 +37,22 @@ export function QueryResults({ query, data, isLoading, endpointEnabled }: QueryR if (!query && !data) return null; return ( - - + + - {endpointEnabled ? "LogsQL query results" : "LogsQL query"} + {execMode === "query" ? "LogsQL query results" : "LogsQL query"} - {query && + {query && ( {query} - } + )} - {!!data && - + {!!data && ( + - } + )} ); } diff --git a/cmd/sql-to-logsql/web/ui/src/components/sql-editor/SQLEditor.tsx b/cmd/sql-to-logsql/web/ui/src/components/sql-editor/SQLEditor.tsx index 506acd7..7d6c63e 100644 --- a/cmd/sql-to-logsql/web/ui/src/components/sql-editor/SQLEditor.tsx +++ b/cmd/sql-to-logsql/web/ui/src/components/sql-editor/SQLEditor.tsx @@ -7,19 +7,37 @@ import { CardHeader, CardTitle, } from "@/components/ui/card.tsx"; -import {useCallback, useEffect, useState} from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button.tsx"; -import {Select, SelectContent, SelectItem, SelectTrigger} from "@/components/ui/select.tsx"; -import {SelectValue} from "@radix-ui/react-select"; -import {DEFAULT_EXAMPLE_ID, EXAMPLES} from "@/components/sql-editor/examples.ts"; -import {COMPLETIONS} from "@/components/sql-editor/complections.ts"; -import {CircleXIcon, CircleCheckBigIcon, PlayIcon, ListFilterIcon} from "lucide-react" -import {Spinner} from "@/components/ui/spinner.tsx"; -import {Badge} from "@/components/ui/badge.tsx"; -import {cn} from "@/lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "@/components/ui/select.tsx"; +import { SelectValue } from "@radix-ui/react-select"; +import { + DEFAULT_EXAMPLE_ID, + EXAMPLES, +} from "@/components/sql-editor/examples.ts"; +import { COMPLETIONS } from "@/components/sql-editor/complections.ts"; +import { + CircleXIcon, + CircleCheckBigIcon, + PlayIcon, + ListFilterIcon, +} from "lucide-react"; +import { Spinner } from "@/components/ui/spinner.tsx"; +import { Badge } from "@/components/ui/badge.tsx"; +import { cn } from "@/lib/utils"; +import { + DateTimeRange, + type DateTimeRangeValue, +} from "@/components/date-time-range"; +import { parseDate } from "chrono-node"; export interface SqlEditorProps { - readonly onRun?: (sql: string) => void; + readonly onRun?: (sql: string, start?: number, end?: number) => void; readonly isLoading?: boolean; readonly error?: string; readonly success?: string; @@ -37,25 +55,42 @@ export function SQLEditor({ }: SqlEditorProps) { const [value, setValue] = useState(DEFAULT_EXAMPLE_ID); const [sql, setSql] = useState(""); + const [timeRange, setTimeRange] = useState({ + from: "1h ago", + to: "now", + }); + const dateStart = useMemo( + () => (timeRange?.from ? parseDate(timeRange?.from)?.valueOf() : undefined), + [timeRange.from], + ); + const dateEnd = useMemo( + () => (timeRange?.to ? parseDate(timeRange?.to)?.valueOf() : undefined), + [timeRange.to], + ); - const runQuery = useCallback((text?: string) => { - if (!onRun || isLoading) { - return; - } - const current = typeof text === "string" ? text : sql; - onRun(current); - }, [onRun, isLoading, sql]); + const runQuery = useCallback( + (text?: string) => { + if (!onRun || isLoading) { + return; + } + const current = typeof text === "string" ? text : sql; + onRun(current, dateStart, dateEnd); + }, + [onRun, isLoading, sql, dateStart, dateEnd], + ); useEffect(() => { const example = EXAMPLES.find((example) => example.id === value); if (example) { - setSql(example.sql ?? ""); + setSql(example.sql ?? ""); } }, [value]); return ( - - + + SQL +