From 0d8efc47656c4bd0ef6df345575e873997397802 Mon Sep 17 00:00:00 2001
From: Maciej Bodek <1871646+insmac@users.noreply.github.com>
Date: Mon, 4 Nov 2024 14:22:22 +0100
Subject: [PATCH 1/4] feat(web-console): add news image zoom (#350)
---
packages/browser-tests/questdb | 2 +-
.../web-console/src/scenes/Layout/index.tsx | 3 +
.../src/scenes/News/image-zoom.tsx | 87 +++++++++++++++++++
.../web-console/src/scenes/News/index.tsx | 41 ++++++++-
.../web-console/src/scenes/News/thumbnail.tsx | 24 +++--
.../web-console/src/store/Console/actions.ts | 7 ++
.../web-console/src/store/Console/reducers.ts | 8 ++
.../src/store/Console/selectors.ts | 7 +-
.../web-console/src/store/Console/types.ts | 15 ++++
packages/web-console/src/utils/questdb.ts | 25 +++---
10 files changed, 199 insertions(+), 20 deletions(-)
create mode 100644 packages/web-console/src/scenes/News/image-zoom.tsx
diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb
index 3ae8efbe0..6442b320c 160000
--- a/packages/browser-tests/questdb
+++ b/packages/browser-tests/questdb
@@ -1 +1 @@
-Subproject commit 3ae8efbe0772c568859f372a2c120055278f3c7b
+Subproject commit 6442b320c1dd2b17ad9ea0f688600e9d7caf9258
diff --git a/packages/web-console/src/scenes/Layout/index.tsx b/packages/web-console/src/scenes/Layout/index.tsx
index 007eb2095..ba14d4f73 100644
--- a/packages/web-console/src/scenes/Layout/index.tsx
+++ b/packages/web-console/src/scenes/Layout/index.tsx
@@ -36,6 +36,7 @@ import { CreateTableDialog } from "../../components/CreateTableDialog"
import { EditorProvider } from "../../providers"
import { Help } from "./help"
import { Warnings } from "./warning"
+import { ImageZoom } from "../News/image-zoom"
import "allotment/dist/style.css"
@@ -62,6 +63,7 @@ const Root = styled.div`
`
const Main = styled.div<{ sideOpened: boolean }>`
+ position: relative;
flex: 1;
display: flex;
width: ${({ sideOpened }) =>
@@ -81,6 +83,7 @@ const Layout = () => {
+
diff --git a/packages/web-console/src/scenes/News/image-zoom.tsx b/packages/web-console/src/scenes/News/image-zoom.tsx
new file mode 100644
index 000000000..00ff19e35
--- /dev/null
+++ b/packages/web-console/src/scenes/News/image-zoom.tsx
@@ -0,0 +1,87 @@
+import React, { useEffect, useRef, useState } from "react"
+import styled from "styled-components"
+import { Box } from "@questdb/react-components"
+import { useSelector, useDispatch } from "react-redux"
+import { selectors, actions } from "../../store"
+import { Thumbnail } from "./thumbnail"
+
+const Root = styled(Box).attrs({ align: "center", justifyContent: "center" })<{
+ visible: boolean
+}>`
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 1000;
+ opacity: ${({ visible }) => (visible ? 1 : 0)};
+ pointer-events: ${({ visible }) => (visible ? "auto" : "none")};
+`
+
+const Overlay = styled.div<{ visible: boolean }>`
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(33, 34, 44, 0.9);
+ opacity: ${({ visible }) => (visible ? 1 : 0)};
+ pointer-events: ${({ visible }) => (visible ? "auto" : "none")};
+ transition: opacity 0.2s ease-in-out;
+`
+
+const Wrapper = styled.div`
+ z-index: 1001;
+
+ img {
+ border: 1px solid ${({ theme }) => theme.color.offWhite};
+ border-radius: ${({ theme }) => theme.borderRadius};
+ }
+`
+
+export const ImageZoom = () => {
+ const imageToZoom = useSelector(selectors.console.getImageToZoom)
+ const dispatch = useDispatch()
+ const rootRef = useRef(null)
+ const [rootWidth, setRootWidth] = useState(0)
+ const [rootHeight, setRootHeight] = useState(0)
+ const activeSidebar = useSelector(selectors.console.getActiveSidebar)
+
+ const handleEsc = (event: KeyboardEvent) => {
+ if (event.key === "Escape") {
+ dispatch(actions.console.setImageToZoom(undefined))
+ }
+ }
+
+ useEffect(() => {
+ if (rootRef.current) {
+ setRootWidth(rootRef.current.offsetWidth)
+ setRootHeight(rootRef.current.offsetHeight)
+ }
+ }, [imageToZoom])
+
+ useEffect(() => {
+ if (activeSidebar === "news") {
+ document.addEventListener("keydown", handleEsc)
+ } else {
+ document.removeEventListener("keydown", handleEsc)
+ }
+ }, [activeSidebar])
+
+ if (activeSidebar !== "news") {
+ return null
+ }
+
+ return (
+
+
+ {imageToZoom && (
+
+
+
+ )}
+
+ )
+}
diff --git a/packages/web-console/src/scenes/News/index.tsx b/packages/web-console/src/scenes/News/index.tsx
index abe9133ac..08503dd72 100644
--- a/packages/web-console/src/scenes/News/index.tsx
+++ b/packages/web-console/src/scenes/News/index.tsx
@@ -60,13 +60,15 @@ const NewsText = styled(Text).attrs({ color: "foreground" })`
font-size: 1.6rem;
}
- p {
+ p,
+ li {
font-size: ${({ theme }) => theme.fontSize.lg};
line-height: 1.75;
}
code {
background-color: ${({ theme }) => theme.color.selection};
+ color: ${({ theme }) => theme.color.pink};
padding: 0.2rem 0.4rem;
border-radius: 0.2rem;
}
@@ -92,6 +94,8 @@ const News = () => {
const [hasUnreadNews, setHasUnreadNews] = useState(false)
const activeSidebar = useSelector(selectors.console.getActiveSidebar)
+ let hoverTimeout: NodeJS.Timeout
+
const getEnterpriseNews = async () => {
setIsLoading(true)
setHasError(false)
@@ -229,10 +233,45 @@ const News = () => {
newsItem.thumbnail[0].thumbnails.large && (
{
+ if (newsItem.thumbnail) {
+ hoverTimeout = setTimeout(() => {
+ if (newsItem && newsItem.thumbnail) {
+ dispatch(
+ actions.console.setImageToZoom({
+ src: newsItem.thumbnail[0].thumbnails
+ .large.url,
+ width:
+ newsItem.thumbnail[0].thumbnails.large
+ .width,
+ height:
+ newsItem.thumbnail[0].thumbnails.large
+ .height,
+ alt: newsItem.title,
+ }),
+ )
+ }
+ }, 500)
+ }
+ },
+ onMouseOut: () => {
+ clearTimeout(hoverTimeout)
+ setTimeout(() => {
+ dispatch(
+ actions.console.setImageToZoom(undefined),
+ )
+ }, 250)
+ },
+ }
+ : {})}
/>
)}
diff --git a/packages/web-console/src/scenes/News/thumbnail.tsx b/packages/web-console/src/scenes/News/thumbnail.tsx
index 2964b5226..71dbd9e41 100644
--- a/packages/web-console/src/scenes/News/thumbnail.tsx
+++ b/packages/web-console/src/scenes/News/thumbnail.tsx
@@ -18,13 +18,12 @@ const Root = styled.div`
}
`
-const ThumbImg = styled.img<{ loaded: boolean }>`
- width: 46rem;
+const ThumbImg = styled.img<{ loaded: boolean; fadeIn?: boolean }>`
height: auto;
- ${({ loaded }) => `
+ ${({ loaded, fadeIn }) => `
opacity: ${loaded ? 1 : 0};
- transition: opacity 0.2s ease-in-out;
+ ${fadeIn && `transition: opacity 0.2s ease-in-out;`}
`}
`
export const Thumbnail = ({
@@ -33,17 +32,27 @@ export const Thumbnail = ({
width,
height,
containerWidth,
+ containerHeight,
+ fadeIn,
+ ...rest
}: {
src: string
alt: string
width: number
height: number
containerWidth: number
+ containerHeight: number
+ fadeIn?: boolean
}) => {
const [isLoaded, setIsLoaded] = useState(false)
- const scaledImageWidth = containerWidth
- const scaledImageHeight = (scaledImageWidth / width) * height
+ let scaledImageWidth = containerWidth
+ let scaledImageHeight = (scaledImageWidth / width) * height
+ if (scaledImageHeight > containerHeight) {
+ const ratio = containerHeight / scaledImageHeight
+ scaledImageHeight = containerHeight
+ scaledImageWidth *= ratio
+ }
useEffect(() => {
const imgElement = new Image()
@@ -55,7 +64,7 @@ export const Thumbnail = ({
}, [src])
return (
-
+
{!isLoaded && }
)
diff --git a/packages/web-console/src/store/Console/actions.ts b/packages/web-console/src/store/Console/actions.ts
index 2a1712147..3961d49f8 100644
--- a/packages/web-console/src/store/Console/actions.ts
+++ b/packages/web-console/src/store/Console/actions.ts
@@ -24,6 +24,7 @@
import {
ConsoleAction,
ConsoleAT,
+ ImageToZoom,
TopPanel,
Sidebar,
BottomPanel,
@@ -43,6 +44,11 @@ const setActiveBottomPanel = (panel: BottomPanel): ConsoleAction => ({
type: ConsoleAT.SET_ACTIVE_BOTTOM_PANEL,
})
+const setImageToZoom = (image?: ImageToZoom): ConsoleAction => ({
+ payload: image,
+ type: ConsoleAT.SET_IMAGE_TO_ZOOM,
+})
+
const toggleSideMenu = (): ConsoleAction => ({
type: ConsoleAT.TOGGLE_SIDE_MENU,
})
@@ -52,4 +58,5 @@ export default {
setActiveTopPanel,
setActiveSidebar,
setActiveBottomPanel,
+ setImageToZoom,
}
diff --git a/packages/web-console/src/store/Console/reducers.ts b/packages/web-console/src/store/Console/reducers.ts
index 480d021fd..904d8757f 100644
--- a/packages/web-console/src/store/Console/reducers.ts
+++ b/packages/web-console/src/store/Console/reducers.ts
@@ -29,6 +29,7 @@ export const initialState: ConsoleStateShape = {
activeTopPanel: "tables",
activeSidebar: undefined,
activeBottomPanel: "zeroState",
+ imageToZoom: undefined,
}
const _console = (
@@ -64,6 +65,13 @@ const _console = (
}
}
+ case ConsoleAT.SET_IMAGE_TO_ZOOM: {
+ return {
+ ...state,
+ imageToZoom: action.payload,
+ }
+ }
+
default:
return state
}
diff --git a/packages/web-console/src/store/Console/selectors.ts b/packages/web-console/src/store/Console/selectors.ts
index f45be1e29..5c3899a71 100644
--- a/packages/web-console/src/store/Console/selectors.ts
+++ b/packages/web-console/src/store/Console/selectors.ts
@@ -21,7 +21,7 @@
* limitations under the License.
*
******************************************************************************/
-import { StoreShape, Sidebar, BottomPanel, TopPanel } from "types"
+import { StoreShape, Sidebar, BottomPanel, TopPanel, ImageToZoom } from "types"
const getSideMenuOpened: (store: StoreShape) => boolean = (store) =>
store.console.sideMenuOpened
@@ -35,9 +35,14 @@ const getActiveSidebar: (store: StoreShape) => Sidebar = (store) =>
const getActiveBottomPanel: (store: StoreShape) => BottomPanel = (store) =>
store.console.activeBottomPanel
+const getImageToZoom: (store: StoreShape) => ImageToZoom | undefined = (
+ store,
+) => store.console.imageToZoom
+
export default {
getSideMenuOpened,
getActiveTopPanel,
getActiveSidebar,
getActiveBottomPanel,
+ getImageToZoom,
}
diff --git a/packages/web-console/src/store/Console/types.ts b/packages/web-console/src/store/Console/types.ts
index 2b0f73891..76a456b0f 100644
--- a/packages/web-console/src/store/Console/types.ts
+++ b/packages/web-console/src/store/Console/types.ts
@@ -28,11 +28,19 @@ export type Sidebar = "news" | "create" | undefined
export type BottomPanel = "result" | "zeroState" | "import"
+export type ImageToZoom = {
+ src: string
+ alt: string
+ width: number
+ height: number
+}
+
export type ConsoleStateShape = Readonly<{
sideMenuOpened: boolean
activeTopPanel: TopPanel
activeSidebar: Sidebar
activeBottomPanel: BottomPanel
+ imageToZoom: ImageToZoom | undefined
}>
export enum ConsoleAT {
@@ -40,6 +48,7 @@ export enum ConsoleAT {
SET_ACTIVE_TOP_PANEL = "CONSOLE/SET_ACTIVE_TOP_PANEL",
SET_ACTIVE_SIDEBAR = "CONSOLE/SET_ACTIVE_SIDEBAR",
SET_ACTIVE_BOTTOM_PANEL = "CONSOLE/SET_ACTIVE_BOTTOM_PANEL",
+ SET_IMAGE_TO_ZOOM = "CONSOLE/SET_IMAGE_TO_ZOOM",
}
type ToggleSideMenuAction = Readonly<{
@@ -61,8 +70,14 @@ type setActiveBottomPanelAction = Readonly<{
type: ConsoleAT.SET_ACTIVE_BOTTOM_PANEL
}>
+type setImageToZoomAction = Readonly<{
+ payload?: ImageToZoom
+ type: ConsoleAT.SET_IMAGE_TO_ZOOM
+}>
+
export type ConsoleAction =
| ToggleSideMenuAction
| setActiveTopPanelAction
| setActiveSidebarAction
| setActiveBottomPanelAction
+ | setImageToZoomAction
diff --git a/packages/web-console/src/utils/questdb.ts b/packages/web-console/src/utils/questdb.ts
index 6e14e72e6..7373ada41 100644
--- a/packages/web-console/src/utils/questdb.ts
+++ b/packages/web-console/src/utils/questdb.ts
@@ -27,7 +27,7 @@ import { eventBus } from "../modules/EventBus"
import { EventType } from "../modules/EventBus/types"
import { AuthPayload } from "../modules/OAuth2/types"
import { StoreKey } from "./localStorage/types"
-import {API_VERSION} from "../consts";
+import { API_VERSION } from "../consts"
type ColumnDefinition = Readonly<{ name: string; type: string }>
@@ -86,12 +86,12 @@ type RawErrorResult = {
}
type RawNoticeResult = {
- ddl: undefined
- dml: undefined
- error: undefined
- notice: ""
- position: undefined
- query: string
+ ddl: undefined
+ dml: undefined
+ error: undefined
+ notice: ""
+ position: undefined
+ query: string
}
type DdlResult = {
@@ -104,7 +104,12 @@ type DmlResult = {
type: Type.DML
}
-type RawResult = RawDqlResult | RawDmlResult | RawDdlResult | RawErrorResult | RawNoticeResult
+type RawResult =
+ | RawDqlResult
+ | RawDmlResult
+ | RawDdlResult
+ | RawErrorResult
+ | RawNoticeResult
export type ErrorResult = RawErrorResult & {
type: Type.ERROR
@@ -112,7 +117,7 @@ export type ErrorResult = RawErrorResult & {
}
export type NoticeResult = RawNoticeResult & {
- type: Type.NOTICE
+ type: Type.NOTICE
}
export type QueryRawResult =
@@ -620,7 +625,7 @@ export class Client {
`chk?${Client.encodeParams({
f: "json",
j: name,
- version: API_VERSION,
+ version: API_VERSION,
})}`,
{ headers: this.commonHeaders },
)
From a75269295771567aebcb21a7686c197505a1751f Mon Sep 17 00:00:00 2001
From: Vlad Ilyushchenko
Date: Tue, 5 Nov 2024 20:24:27 +0000
Subject: [PATCH 2/4] chore: highlight integer numbers that include `_`
(underscore) separator (#353)
* chore: highlight integers that include _ as a delimiter
* sync master
---
packages/browser-tests/questdb | 2 +-
.../src/scenes/Editor/Monaco/questdb-sql/language.ts | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb
index 6442b320c..aa44288ce 160000
--- a/packages/browser-tests/questdb
+++ b/packages/browser-tests/questdb
@@ -1 +1 @@
-Subproject commit 6442b320c1dd2b17ad9ea0f688600e9d7caf9258
+Subproject commit aa44288ce957601c92df80659bb882a3b8b06292
diff --git a/packages/web-console/src/scenes/Editor/Monaco/questdb-sql/language.ts b/packages/web-console/src/scenes/Editor/Monaco/questdb-sql/language.ts
index f07a59ec7..3ab7f7288 100644
--- a/packages/web-console/src/scenes/Editor/Monaco/questdb-sql/language.ts
+++ b/packages/web-console/src/scenes/Editor/Monaco/questdb-sql/language.ts
@@ -109,9 +109,9 @@ export const language: monaco.languages.IMonarchLanguage = {
],
],
numbers: [
- [/0[xX][0-9a-fA-F]*/, "number"],
- [/[$][+-]*\d*(\.\d*)?/, "number"],
- [/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, "number"],
+ [/0[xX][0-9a-fA-F]*/, "number"], // hex integers
+ [/[+-]?\d+((_)?\d+)*[Ll]?/, "number"], // integers
+ [/[+-]?\d*(\.\d*)?[Ee]/, "number"], // floating point number
],
strings: [
[/N'/, { token: "string", next: "@string" }],
From 46af9fb8749c68b02609a161fed2ca0d2b7c9c19 Mon Sep 17 00:00:00 2001
From: glasstiger <94906625+glasstiger@users.noreply.github.com>
Date: Wed, 6 Nov 2024 12:41:45 +0000
Subject: [PATCH 3/4] chore(ui): handle and display error received while
scrolling the grid (#348)
* chore(ui): handle and display error received while scrolling the grid
* test
* improved test
* merge
* update submodule
* update submodule
* update submodule
* update submodule
---------
Co-authored-by: Vlad Ilyushchenko
---
.../cypress/integration/console/grid.spec.js | 13 +++++++
packages/browser-tests/questdb | 2 +-
.../web-console/src/scenes/Result/index.tsx | 34 +++++++++++++------
3 files changed, 38 insertions(+), 11 deletions(-)
diff --git a/packages/browser-tests/cypress/integration/console/grid.spec.js b/packages/browser-tests/cypress/integration/console/grid.spec.js
index 25460c2eb..933909c5a 100644
--- a/packages/browser-tests/cypress/integration/console/grid.spec.js
+++ b/packages/browser-tests/cypress/integration/console/grid.spec.js
@@ -59,6 +59,19 @@ describe("questdb grid", () => {
cy.getGridViewport().scrollTo("bottom");
});
+ it("multiple scrolls till the bottom with error", () => {
+ const rows = 1200;
+ cy.typeQuery(`select simulate_crash('P') from long_sequence(${rows})`);
+ cy.runLine();
+
+ cy.getGridViewport().scrollTo(0, 999 * rowHeight);
+ cy.getCollapsedNotifications().should("contain", "1,200 rows in");
+
+ cy.getGridViewport().scrollTo("bottom");
+ cy.wait(100);
+ cy.getCollapsedNotifications().should("contain", "HTTP 400 (Bad request)");
+ });
+
it("copy cell into the clipboard", () => {
cy.typeQuery("select x from long_sequence(10)");
cy.runLine();
diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb
index aa44288ce..1620d78e5 160000
--- a/packages/browser-tests/questdb
+++ b/packages/browser-tests/questdb
@@ -1 +1 @@
-Subproject commit aa44288ce957601c92df80659bb882a3b8b06292
+Subproject commit 1620d78e560d08db2ca8475262fd84879299fcee
diff --git a/packages/web-console/src/scenes/Result/index.tsx b/packages/web-console/src/scenes/Result/index.tsx
index 843fecb3d..eddb3adce 100644
--- a/packages/web-console/src/scenes/Result/index.tsx
+++ b/packages/web-console/src/scenes/Result/index.tsx
@@ -24,7 +24,7 @@
import $ from "jquery"
import React, { useContext, useEffect, useRef, useState } from "react"
-import { useSelector } from "react-redux"
+import { useDispatch, useSelector } from "react-redux"
import styled from "styled-components"
import { Download2, Refresh } from "@styled-icons/remix-line"
import { Reset } from "@styled-icons/boxicons-regular"
@@ -40,8 +40,8 @@ import {
Text,
Tooltip,
} from "../../components"
-import { selectors } from "../../store"
-import { color, QueryRawResult } from "../../utils"
+import { actions, selectors } from "../../store"
+import {color, ErrorResult, QueryRawResult} from "../../utils"
import * as QuestDB from "../../utils/questdb"
import { ResultViewMode } from "scenes/Console/types"
import { Button } from "@questdb/react-components"
@@ -49,6 +49,8 @@ import type { IQuestDBGrid } from "../../js/console/grid.js"
import { eventBus } from "../../modules/EventBus"
import { EventType } from "../../modules/EventBus/types"
import { QuestContext } from "../../providers"
+import {QueryInNotification} from "../Editor/Monaco/query-in-notification";
+import {NotificationType} from "../../store/Query/types";
const Root = styled.div`
display: flex;
@@ -98,17 +100,29 @@ const Result = ({ viewMode }: { viewMode: ResultViewMode }) => {
const activeSidebar = useSelector(selectors.console.getActiveSidebar)
const gridRef = useRef()
const [gridFreezeLeftState, setGridFreezeLeftState] = useState(0)
-
+ const dispatch = useDispatch()
+
useEffect(() => {
const _grid = grid(
document.getElementById("grid"),
async function (sql, lo, hi, rendererFn: (data: QueryRawResult) => void) {
- const result = await quest.queryRaw(sql, {
- limit: `${lo},${hi}`,
- nm: true,
- })
- if (result.type === QuestDB.Type.DQL) {
- rendererFn(result)
+ try {
+ const result = await quest.queryRaw(sql, {
+ limit: `${lo},${hi}`,
+ nm: true,
+ })
+ if (result.type === QuestDB.Type.DQL) {
+ rendererFn(result)
+ }
+ } catch (err) {
+ dispatch(actions.query.stopRunning())
+ dispatch(
+ actions.query.addNotification({
+ content: {(err as ErrorResult).error},
+ sideContent: ,
+ type: NotificationType.ERROR,
+ }),
+ )
}
},
)
From 0494e85ef2f996e6b6f4f3b117fea18d1c223618 Mon Sep 17 00:00:00 2001
From: glasstiger <94906625+glasstiger@users.noreply.github.com>
Date: Thu, 21 Nov 2024 11:28:16 +0000
Subject: [PATCH 4/4] feat(ui): support for ID token (#355)
* feat(ui): support for ID token
* handle undefined oidc host
* submodule update
---
.../cypress/integration/auth/auth.spec.js | 20 ++++---------
packages/browser-tests/questdb | 2 +-
.../web-console/src/modules/OAuth2/types.ts | 2 ++
.../web-console/src/modules/OAuth2/utils.ts | 30 +++++++++++--------
.../src/providers/AuthProvider.tsx | 23 +++++++-------
.../src/providers/QuestProvider/index.tsx | 8 ++---
.../src/providers/SettingsProvider/types.ts | 1 +
.../web-console/src/store/Telemetry/epics.ts | 4 +--
packages/web-console/src/utils/questdb.ts | 4 +--
9 files changed, 47 insertions(+), 47 deletions(-)
diff --git a/packages/browser-tests/cypress/integration/auth/auth.spec.js b/packages/browser-tests/cypress/integration/auth/auth.spec.js
index eef68bbdc..d634985c9 100644
--- a/packages/browser-tests/cypress/integration/auth/auth.spec.js
+++ b/packages/browser-tests/cypress/integration/auth/auth.spec.js
@@ -32,12 +32,10 @@ describe("Auth - UI", () => {
"acl.basic.auth.realm.enabled": false,
"acl.oidc.enabled": false,
"acl.oidc.client.id": null,
- "acl.oidc.host": null,
- "acl.oidc.port": null,
- "acl.oidc.tls.enabled": null,
"acl.oidc.authorization.endpoint": null,
"acl.oidc.token.endpoint": null,
"acl.oidc.pkce.required": null,
+ "acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
@@ -59,12 +57,10 @@ describe("Auth - OIDC", () => {
"acl.basic.auth.realm.enabled": false,
"acl.oidc.enabled": true,
"acl.oidc.client.id": "test",
- "acl.oidc.host": "host",
- "acl.oidc.port": 9999,
- "acl.oidc.tls.enabled": true,
- "acl.oidc.authorization.endpoint": "/auth",
- "acl.oidc.token.endpoint": "/token",
+ "acl.oidc.authorization.endpoint": "https://host:9999/auth",
+ "acl.oidc.token.endpoint": "https://host:9999/token",
"acl.oidc.pkce.required": true,
+ "acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
@@ -86,12 +82,10 @@ describe("Auth - Basic", () => {
"acl.basic.auth.realm.enabled": true,
"acl.oidc.enabled": false,
"acl.oidc.client.id": null,
- "acl.oidc.host": null,
- "acl.oidc.port": null,
- "acl.oidc.tls.enabled": null,
"acl.oidc.authorization.endpoint": null,
"acl.oidc.token.endpoint": null,
"acl.oidc.pkce.required": null,
+ "acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
@@ -111,12 +105,10 @@ describe("Auth - Disabled", () => {
"acl.basic.auth.realm.enabled": true,
"acl.oidc.enabled": false,
"acl.oidc.client.id": null,
- "acl.oidc.host": null,
- "acl.oidc.port": null,
- "acl.oidc.tls.enabled": null,
"acl.oidc.authorization.endpoint": null,
"acl.oidc.token.endpoint": null,
"acl.oidc.pkce.required": null,
+ "acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb
index 1620d78e5..ad57a7eff 160000
--- a/packages/browser-tests/questdb
+++ b/packages/browser-tests/questdb
@@ -1 +1 @@
-Subproject commit 1620d78e560d08db2ca8475262fd84879299fcee
+Subproject commit ad57a7effc10ba1c9d59db5edd6e0ca7ffca3ebc
diff --git a/packages/web-console/src/modules/OAuth2/types.ts b/packages/web-console/src/modules/OAuth2/types.ts
index 6e675add8..de7f20a1a 100644
--- a/packages/web-console/src/modules/OAuth2/types.ts
+++ b/packages/web-console/src/modules/OAuth2/types.ts
@@ -1,7 +1,9 @@
export type AuthPayload = {
access_token: string
+ id_token: string
refresh_token: string
token_type: string
expires_in: number
expires_at?: string
+ groups_encoded_in_token?: boolean
}
diff --git a/packages/web-console/src/modules/OAuth2/utils.ts b/packages/web-console/src/modules/OAuth2/utils.ts
index 8a7836a4b..099cc23ca 100644
--- a/packages/web-console/src/modules/OAuth2/utils.ts
+++ b/packages/web-console/src/modules/OAuth2/utils.ts
@@ -9,27 +9,33 @@ type TokenPayload = Partial<{
refresh_token: string
}>
-const getBaseURL = (config: Settings) => {
- return `${config["acl.oidc.tls.enabled"] ? "https" : "http"}://${
- config["acl.oidc.host"]
- }:${config["acl.oidc.port"]}`
+const getBaseURL = (settings: Settings) => {
+ // if there is no host in settings, no need to construct base URL at all
+ if (!settings["acl.oidc.host"]) {
+ return "";
+ }
+
+ // if there is host in settings, we are in legacy mode, and we should construct the base URL
+ return `${settings["acl.oidc.tls.enabled"] ? "https" : "http"}://${
+ settings["acl.oidc.host"]
+ }:${settings["acl.oidc.port"]}`
}
export const getAuthorisationURL = ({
- config,
+ settings,
code_challenge = null,
login,
redirect_uri,
}: {
- config: Settings
+ settings: Settings
code_challenge: string | null
login?: boolean
redirect_uri: string
}) => {
const params = {
- client_id: config["acl.oidc.client.id"] || "",
+ client_id: settings["acl.oidc.client.id"] || "",
response_type: "code",
- scope: config["acl.oidc.scope"] || "openid",
+ scope: settings["acl.oidc.scope"] || "openid",
redirect_uri,
}
@@ -43,8 +49,8 @@ export const getAuthorisationURL = ({
}
return (
- getBaseURL(config) +
- config["acl.oidc.authorization.endpoint"] +
+ getBaseURL(settings) +
+ settings["acl.oidc.authorization.endpoint"] +
"?" +
urlParams
)
@@ -70,5 +76,5 @@ export const getAuthToken = async (
)
}
-export const hasUIAuth = (config: Settings) =>
- config["acl.enabled"] && !config["acl.basic.auth.realm.enabled"]
+export const hasUIAuth = (settings: Settings) =>
+ settings["acl.enabled"] && !settings["acl.basic.auth.realm.enabled"]
diff --git a/packages/web-console/src/providers/AuthProvider.tsx b/packages/web-console/src/providers/AuthProvider.tsx
index b6783cb16..1b1eae9e3 100644
--- a/packages/web-console/src/providers/AuthProvider.tsx
+++ b/packages/web-console/src/providers/AuthProvider.tsx
@@ -77,14 +77,13 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
)
const [state, dispatch] = useReducer(reducer, initialState)
- const setAuthToken = (tokenResponse: AuthPayload) => {
- if (tokenResponse.access_token) {
+ const setAuthToken = (tokenResponse: AuthPayload, settings: Settings) => {
+ if (tokenResponse.access_token && tokenResponse.id_token) {
+ tokenResponse.groups_encoded_in_token = settings["acl.oidc.groups.encoded.in.token"]
+ tokenResponse.expires_at = getTokenExpirationDate(tokenResponse.expires_in).toString() // convert from the sec offset
setValue(
StoreKey.AUTH_PAYLOAD,
- JSON.stringify({
- ...tokenResponse,
- expires_at: getTokenExpirationDate(tokenResponse.expires_in), // convert from the sec offset
- }),
+ JSON.stringify(tokenResponse),
)
// if the token payload does not contain the rolling refresh token, we'll keep the old one
if (tokenResponse.refresh_token) {
@@ -119,7 +118,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
client_id: settings["acl.oidc.client.id"],
})
const tokenResponse = await response.json()
- setAuthToken(tokenResponse)
+ setAuthToken(tokenResponse, settings)
return tokenResponse
}
@@ -175,15 +174,15 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
// User is authenticated already
if (authPayload !== "") {
- const token = JSON.parse(authPayload)
+ const tokenResponse = JSON.parse(authPayload)
// Check if the token expired or is about to in 30 seconds
if (
- new Date(token.expires_at).getTime() - Date.now() < 30000 &&
+ new Date(tokenResponse.expires_at).getTime() - Date.now() < 30000 &&
getValue(StoreKey.AUTH_REFRESH_TOKEN) !== ""
) {
await refreshAuthToken(settings)
} else {
- setSessionData(token)
+ setSessionData(tokenResponse)
}
} else {
// User has just been redirected back from the OAuth2 provider and has the code
@@ -198,7 +197,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
redirect_uri: settings["acl.oidc.redirect.uri"] || window.location.origin + window.location.pathname,
})
const tokenResponse = await response.json()
- setAuthToken(tokenResponse)
+ setAuthToken(tokenResponse, settings)
} catch (e) {
throw e
}
@@ -253,7 +252,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const code_verifier = generateCodeVerifier(settings)
const code_challenge = generateCodeChallenge(code_verifier)
window.location.href = getAuthorisationURL({
- config: settings,
+ settings,
code_challenge,
login,
redirect_uri: settings["acl.oidc.redirect.uri"] || window.location.href,
diff --git a/packages/web-console/src/providers/QuestProvider/index.tsx b/packages/web-console/src/providers/QuestProvider/index.tsx
index ee1d1a592..8bd7f0d63 100644
--- a/packages/web-console/src/providers/QuestProvider/index.tsx
+++ b/packages/web-console/src/providers/QuestProvider/index.tsx
@@ -83,7 +83,7 @@ export const QuestProvider = ({ children }: PropsWithChildren) => {
const setupClient = async (sessionData: Partial) => {
questClient.setCommonHeaders({
- Authorization: `Bearer ${sessionData.access_token}`,
+ Authorization: `Bearer ${sessionData.groups_encoded_in_token ? sessionData.id_token : sessionData.access_token}`,
})
questClient.refreshTokenMethod = () => {
@@ -101,11 +101,11 @@ export const QuestProvider = ({ children }: PropsWithChildren) => {
}, [sessionData])
useEffect(() => {
- const token = getValue(StoreKey.REST_TOKEN)
+ const restToken = getValue(StoreKey.REST_TOKEN)
// User has provided the basic auth credentials
- if (token) {
+ if (restToken) {
questClient.setCommonHeaders({
- Authorization: `Bearer ${token}`,
+ Authorization: `Bearer ${restToken}`,
})
void finishAuthCheck()
} else {
diff --git a/packages/web-console/src/providers/SettingsProvider/types.ts b/packages/web-console/src/providers/SettingsProvider/types.ts
index 4c78c2652..81919aed4 100644
--- a/packages/web-console/src/providers/SettingsProvider/types.ts
+++ b/packages/web-console/src/providers/SettingsProvider/types.ts
@@ -4,6 +4,7 @@ export type Settings = Partial<{
"release.version": string
"acl.enabled": boolean
"acl.basic.auth.realm.enabled": boolean
+ "acl.oidc.groups.encoded.in.token": boolean
"acl.oidc.enabled": boolean
"acl.oidc.client.id": string
"acl.oidc.redirect.uri": string
diff --git a/packages/web-console/src/store/Telemetry/epics.ts b/packages/web-console/src/store/Telemetry/epics.ts
index f8fdf83bf..082a0be24 100644
--- a/packages/web-console/src/store/Telemetry/epics.ts
+++ b/packages/web-console/src/store/Telemetry/epics.ts
@@ -59,9 +59,9 @@ export const getConfig: Epic = (
? getValue(StoreKey.AUTH_PAYLOAD)
: "{}"
const token = JSON.parse(authPayload) as AuthPayload
- if (token.access_token) {
+ if (token.access_token && token.id_token) {
quest.setCommonHeaders({
- Authorization: `Bearer ${token.access_token}`,
+ Authorization: `Bearer ${token.groups_encoded_in_token ? token.id_token : token.access_token}`,
})
} else {
const restToken = getValue(StoreKey.REST_TOKEN)
diff --git a/packages/web-console/src/utils/questdb.ts b/packages/web-console/src/utils/questdb.ts
index 7373ada41..9c16ad98a 100644
--- a/packages/web-console/src/utils/questdb.ts
+++ b/packages/web-console/src/utils/questdb.ts
@@ -344,10 +344,10 @@ export class Client {
if (Client.numOfPendingQueries === 0) {
clearInterval(interval)
const newToken = await this.refreshTokenMethod()
- if (newToken.access_token) {
+ if (newToken.access_token && newToken.id_token) {
this.setCommonHeaders({
...this.commonHeaders,
- Authorization: `Bearer ${newToken.access_token}`,
+ Authorization: `Bearer ${newToken.groups_encoded_in_token ? newToken.id_token : newToken.access_token}`,
})
}
Client.refreshTokenPending = false