From 4d9ab8fe503a65320e8c4b20c2abae185030560d Mon Sep 17 00:00:00 2001 From: glasstiger Date: Thu, 24 Apr 2025 02:05:38 +0100 Subject: [PATCH 01/28] feat(ui): instance name --- packages/browser-tests/questdb | 2 +- .../src/components/TopBar/toolbar.tsx | 7 ++++- .../web-console/src/utils/questdb/client.ts | 30 +++++++++++++++++++ packages/web-console/webpack.config.js | 3 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb index d6873caf2..9f8eb5866 160000 --- a/packages/browser-tests/questdb +++ b/packages/browser-tests/questdb @@ -1 +1 @@ -Subproject commit d6873caf20e799b7b9865c18109bed50332e2ed7 +Subproject commit 9f8eb58668d56792749ea9ad2c965c42cb005b98 diff --git a/packages/web-console/src/components/TopBar/toolbar.tsx b/packages/web-console/src/components/TopBar/toolbar.tsx index 87b46c5dc..1f0254039 100644 --- a/packages/web-console/src/components/TopBar/toolbar.tsx +++ b/packages/web-console/src/components/TopBar/toolbar.tsx @@ -128,7 +128,12 @@ export const Toolbar = () => { {serverDetails && serverDetails.instance_name && ( - + quest.saveConfig({ + "instance_name": "db1", + "instance_rgb": "" + })} + > {serverDetails.instance_name} )} diff --git a/packages/web-console/src/utils/questdb/client.ts b/packages/web-console/src/utils/questdb/client.ts index 202802056..dc0db7046 100644 --- a/packages/web-console/src/utils/questdb/client.ts +++ b/packages/web-console/src/utils/questdb/client.ts @@ -416,6 +416,36 @@ export class Client { }) } + async saveConfig(config: any): Promise { + const formData = new FormData() + formData.append("config", JSON.stringify(config)) + + return new Promise((resolve, reject) => { + let request = new XMLHttpRequest() + request.open("POST", "config") + Object.keys(this.commonHeaders).forEach((key) => { + request.setRequestHeader(key, this.commonHeaders[key]) + }) + request.onload = (_e) => { + if (request.status === 200) { + resolve(JSON.parse(request.response)) + } else { + reject({ + status: request.status, + statusText: request.statusText, + }) + } + } + request.onerror = () => { + reject({ + status: request.status, + statusText: request.statusText, + }) + } + request.send(formData) + }) + } + async exportQueryToCsv(query: string) { try { const response: Response = await fetch( diff --git a/packages/web-console/webpack.config.js b/packages/web-console/webpack.config.js index 69541ebab..bab049e3e 100644 --- a/packages/web-console/webpack.config.js +++ b/packages/web-console/webpack.config.js @@ -85,7 +85,8 @@ module.exports = { { context: [ config.contextPath + "/imp", config.contextPath + "/exp", config.contextPath + "/exec", - config.contextPath + "/chk", config.contextPath + "/settings", config.contextPath + "/warnings" + config.contextPath + "/chk", config.contextPath + "/settings", config.contextPath + "/warnings", + config.contextPath + "/config" ], target: config.backendUrl, }, From fac6306a1f04960859a1f48b372f87ced9589a54 Mon Sep 17 00:00:00 2001 From: emrberk Date: Tue, 29 Apr 2025 13:06:32 +0300 Subject: [PATCH 02/28] feat(web-console): instance naming UI --- packages/browser-tests/questdb | 2 +- .../TopBar/InstanceSettingsPopper.tsx | 197 ++++++++++++++++++ .../src/components/TopBar/toolbar.tsx | 151 +++++++++++--- .../web-console/src/utils/questdb/client.ts | 19 +- .../web-console/src/utils/questdb/types.ts | 6 + 5 files changed, 342 insertions(+), 33 deletions(-) create mode 100644 packages/web-console/src/components/TopBar/InstanceSettingsPopper.tsx diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb index 9f8eb5866..e59924475 160000 --- a/packages/browser-tests/questdb +++ b/packages/browser-tests/questdb @@ -1 +1 @@ -Subproject commit 9f8eb58668d56792749ea9ad2c965c42cb005b98 +Subproject commit e599244757240330aa1b91b1c527f3a938a5ca52 diff --git a/packages/web-console/src/components/TopBar/InstanceSettingsPopper.tsx b/packages/web-console/src/components/TopBar/InstanceSettingsPopper.tsx new file mode 100644 index 000000000..c45dee5e7 --- /dev/null +++ b/packages/web-console/src/components/TopBar/InstanceSettingsPopper.tsx @@ -0,0 +1,197 @@ +import React, { useState, useRef } from "react" +import styled from "styled-components" +import { Box, Button, Input } from "@questdb/react-components" +import { Text } from "../Text" +import { PopperToggle } from "../PopperToggle" +import { Config } from "../../utils/questdb/types" + +const Wrapper = styled.div` + position: absolute; + left: 13.5rem; + top: 4.5rem; + margin-top: 0.5rem; + background: ${({ theme }) => theme.color.backgroundDarker}; + border: 1px solid ${({ theme }) => theme.color.gray1}; + border-radius: 0.4rem; + padding: 1.5rem; + width: 32rem; + z-index: 1000; +` + +const ColorSelector = styled.div` + display: flex; + gap: 1rem; +` + +const ColorOption = styled.div<{ color: string; selected: boolean }>` + width: 3rem; + height: 3rem; + border-radius: 0.4rem; + cursor: pointer; + background: ${({ color, theme }) => { + switch (color) { + case "r": + return "#c7072d" + case "g": + return "#00aa3b" + case "b": + return "#007aff" + case "default": + return theme.color.backgroundLighter + default: + return "transparent" + } + }}; + border: 2px solid ${({ theme, selected }) => (selected ? theme.color.foreground : "transparent")}; +` + +const Buttons = styled(Box)` + margin-top: 1.5rem; + gap: 1rem; + justify-content: flex-end; +` + +const FormGroup = styled(Box).attrs({ flexDirection: "column", gap: "0.5rem" })` + width: 100%; +` + +const StyledInput = styled(Input)` + width: 100%; + background: ${({ theme }) => theme.color.selection}; + border: 1px solid ${({ theme }) => theme.color.gray1}; + border-radius: 0.4rem; + color: ${({ theme }) => theme.color.foreground}; + + &:focus { + outline: none; + border-color: ${({ theme }) => theme.color.foreground}; + } + + &::placeholder { + color: ${({ theme }) => theme.color.gray2}; + } +` + +const TextArea = styled.textarea` + width: 100%; + min-height: 8rem; + padding: 0.8rem; + background: ${({ theme }) => theme.color.selection}; + border: 1px solid ${({ theme }) => theme.color.gray1}; + border-radius: 0.4rem; + color: ${({ theme }) => theme.color.foreground}; + font-family: inherit; + font-size: 1.4rem; + line-height: 1.5; + resize: vertical; + + &:focus { + outline: none; + border-color: ${({ theme }) => theme.color.foreground}; + } + + &::placeholder { + color: ${({ theme }) => theme.color.gray2}; + } +` + +const FormLabel = styled(Text)<{ align?: 'left' | 'center' }>` + text-align: ${props => props.align || 'left'}; + width: 100%; +` + +const ErrorText = styled(Text)` + color: ${({ theme }) => theme.color.red}; + font-size: 1.2rem; + margin-top: 0.2rem; +` + +type Props = { + active: boolean + onToggle: (active: boolean) => void + values: Config + onSave: (values: Config) => Promise + onValuesChange: (values: Config) => void +} + +export const InstanceSettingsPopper = ({ + active, + onToggle, + values, + onSave, + onValuesChange, +}: Props) => { + const [error, setError] = useState(null) + + const handleSave = async () => { + if (!values?.instance_name?.trim()) { + setError("Instance name is required") + return + } + setError(null) + await onSave(values) + onToggle(false) + } + + const handleNameChange = (e: React.ChangeEvent) => { + const newValues = { ...values, instance_name: e.target.value } + onValuesChange(newValues) + if (error && e.target.value.trim()) { + setError(null) + } + } + + return ( + + + + + Instance Name + + {error && {error}} + + + Description +