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
+