From 7bd1f075f6434387290b22bd238a5d528289bc4a Mon Sep 17 00:00:00 2001 From: Peter Kosztolanyi Date: Sun, 12 Jan 2025 09:36:34 +0000 Subject: [PATCH] Fix code formatting in Preview Web UI --- .../resources/webapp-preview/package.json | 4 +- .../resources/webapp-preview/src/api/base.ts | 16 +- .../webapp-preview/src/api/webapp/api.ts | 22 +- .../webapp-preview/src/api/webapp/auth.ts | 21 +- .../webapp-preview/src/components/Auth.tsx | 45 +- .../src/components/AuthContext.ts | 30 +- .../src/components/AuthProvider.tsx | 165 +++-- .../src/components/CodeBlock.tsx | 70 ++- .../src/components/Dashboard.tsx | 352 +++++------ .../src/components/DemoComponents.tsx | 207 ++++--- .../webapp-preview/src/components/Layout.tsx | 563 +++++++++--------- .../webapp-preview/src/components/Login.tsx | 199 ++++--- .../src/components/MetricCard.tsx | 112 ++-- .../src/components/SnackbarContext.ts | 20 +- .../src/components/SnackbarProvider.tsx | 74 +-- .../webapp-preview/src/components/Workers.tsx | 25 +- .../webapp-preview/src/store/config.ts | 70 +-- .../webapp-preview/src/store/index.ts | 2 +- .../webapp-preview/src/utils/merge.ts | 14 +- .../webapp-preview/src/utils/utils.ts | 160 ++--- 20 files changed, 1080 insertions(+), 1091 deletions(-) diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/package.json b/core/trino-web-ui/src/main/resources/webapp-preview/package.json index e1cf68713e9b..fbffeec2ede1 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/package.json +++ b/core/trino-web-ui/src/main/resources/webapp-preview/package.json @@ -10,8 +10,8 @@ "package": "npm install && npm run build", "package:clean": "npm clean-install && npm run build", "preview": "vite preview", - "prettier:format": "prettier --write \"./src/*.{ts,tsx}\"", - "prettier:check": "prettier --check \"./src/*.{ts,tsx}\"", + "prettier:format": "prettier --write \"./src/**/*.{ts,tsx}\"", + "prettier:check": "prettier --check \"./src/**/*.{ts,tsx}\"", "check": "npm install && npm run lint && npm run prettier:check", "check:clean": "npm clean-install && npm run lint && npm run prettier:check" }, diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/api/base.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/api/base.ts index 1a3f671083d2..a6929d48632f 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/api/base.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/api/base.ts @@ -20,7 +20,7 @@ export interface ApiResponse { } export class ClientApi { - axiosInstance: AxiosInstance = axios.create(); + axiosInstance: AxiosInstance = axios.create() paramsToQueryString = (params: Record): string => { let queryString = '' @@ -32,13 +32,13 @@ export class ClientApi { fetchData = async (url: string, method: 'GET' | 'POST', params?: P): Promise> => { try { - let response: AxiosResponse; + let response: AxiosResponse if (method === 'GET') { response = await this.axiosInstance.get(url) } else if (method === 'POST') { response = await this.axiosInstance.post(url, params) } else { - throw new Error(`Unsupported HTTP method: ${method}`); + throw new Error(`Unsupported HTTP method: ${method}`) } return { status: response.status, @@ -53,17 +53,17 @@ export class ClientApi { message: axiosError.message, } } - throw error; + throw error } } get = async (url: string, params: Record = {}): Promise> => { - return this.fetchData(url + this.paramsToQueryString(params), 'GET'); + return this.fetchData(url + this.paramsToQueryString(params), 'GET') } - post = async(url: string, body: Record = {}): Promise> => { - return this.fetchData(url, 'POST', body); + post = async (url: string, body: Record = {}): Promise> => { + return this.fetchData(url, 'POST', body) } } -export const api = new ClientApi(); +export const api = new ClientApi() diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/api.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/api.ts index 1918efbda326..dda2ad0fbb89 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/api.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/api.ts @@ -14,17 +14,17 @@ import { api, ApiResponse } from '../base.ts' export interface Stats { - runningQueries: number; - blockedQueries: number; - queuedQueries: number; - activeCoordinators: number; - activeWorkers: number; - runningDrivers: number; - totalAvailableProcessors: number; - reservedMemory: number; - totalInputRows: number; - totalInputBytes: number; - totalCpuTimeSecs: number; + runningQueries: number + blockedQueries: number + queuedQueries: number + activeCoordinators: number + activeWorkers: number + runningDrivers: number + totalAvailableProcessors: number + reservedMemory: number + totalInputRows: number + totalInputBytes: number + totalCpuTimeSecs: number } export async function statsApi(): Promise> { diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/auth.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/auth.ts index c921fb33c23f..8a07293aa304 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/auth.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/auth.ts @@ -11,26 +11,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { api, ApiResponse } from "../base"; +import { api, ApiResponse } from '../base' -export interface AuthInfo -{ - authType: "insecure" | "form" | "fixed" | "oauth2" | null; - passwordAllowed: boolean; - authenticated: boolean; - username?: string; +export interface AuthInfo { + authType: 'insecure' | 'form' | 'fixed' | 'oauth2' | null + passwordAllowed: boolean + authenticated: boolean + username?: string } -export type Empty = object; +export type Empty = object export async function authInfoApi(): Promise> { - return api.get('/ui/preview/auth/info') + return api.get('/ui/preview/auth/info') } export async function loginApi(body: Record): Promise> { - return api.post('/ui/preview/auth/login', body) + return api.post('/ui/preview/auth/login', body) } export async function logoutApi(): Promise> { - return api.get('/ui/preview/auth/logout') + return api.get('/ui/preview/auth/logout') } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Auth.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Auth.tsx index c257eed4ae5a..b8f21ff3d830 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Auth.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Auth.tsx @@ -11,33 +11,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Box, CircularProgress, Alert, Typography } from "@mui/material"; +import { Box, CircularProgress, Alert, Typography } from '@mui/material' import { useAuth } from './AuthContext' -import { Texts } from "../constant"; -import { Login } from './Login'; +import { Texts } from '../constant' +import { Login } from './Login' export const Auth = () => { - const { authInfo, error, loading } = useAuth(); + const { authInfo, error, loading } = useAuth() - if (!authInfo) { - return ( - - {loading ? - <> - - {Texts.Auth.Authenticating} - : error && - - {error} - - } - - ) - } + if (!authInfo) { + return ( + + {loading ? ( + <> + + {Texts.Auth.Authenticating} + + ) : ( + error && {error} + )} + + ) + } - if (authInfo.authType === 'form' && !authInfo?.authenticated) { - return - } + if (authInfo.authType === 'form' && !authInfo?.authenticated) { + return + } - return
{Texts.Auth.NotImplementedAuthType}
+ return
{Texts.Auth.NotImplementedAuthType}
} diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthContext.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthContext.ts index 42dd9204c4fd..944281667120 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthContext.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthContext.ts @@ -11,27 +11,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createContext, useContext } from "react"; -import { AuthInfo } from '../api/webapp/auth'; +import { createContext, useContext } from 'react' +import { AuthInfo } from '../api/webapp/auth' export interface LogoutParams { - redirect?: boolean; + redirect?: boolean } interface AuthContextType { - authInfo: AuthInfo | null; - login: (username: string, password: string) => Promise; - logout: (params: LogoutParams) => void; - loading: boolean; - error: string | null; + authInfo: AuthInfo | null + login: (username: string, password: string) => Promise + logout: (params: LogoutParams) => void + loading: boolean + error: string | null } -export const AuthContext = createContext(undefined); +export const AuthContext = createContext(undefined) export const useAuth = (): AuthContextType => { - const context = useContext(AuthContext); - if (context === undefined) { - throw new Error('AuthContext must be used within an AuthProvider'); - } - return context; -}; + const context = useContext(AuthContext) + if (context === undefined) { + throw new Error('AuthContext must be used within an AuthProvider') + } + return context +} diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthProvider.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthProvider.tsx index 8153a1aae206..f6d9cf28e2c9 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthProvider.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/AuthProvider.tsx @@ -11,104 +11,101 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect, useState, ReactNode } from 'react'; -import { AuthContext, LogoutParams } from "./AuthContext"; -import { ApiResponse } from "../api/base.ts"; +import React, { useEffect, useState, ReactNode } from 'react' +import { AuthContext, LogoutParams } from './AuthContext' +import { ApiResponse } from '../api/base.ts' import { authInfoApi, loginApi, logoutApi, AuthInfo, Empty } from '../api/webapp/auth' -import { Texts } from '../constant'; +import { Texts } from '../constant' interface AuthProviderProps { - children: ReactNode; // This will allow any valid JSX (components, elements) as children + children: ReactNode // This will allow any valid JSX (components, elements) as children } interface ApiCallOptions { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - apiFn: (body: any) => Promise>; - onSuccess: (result?: AuthInfo | Empty) => void; - params?: Record; - onForbidden?: (response: ApiResponse) => void; - onError?: (response: ApiResponse) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + apiFn: (body: any) => Promise> + onSuccess: (result?: AuthInfo | Empty) => void + params?: Record + onForbidden?: (response: ApiResponse) => void + onError?: (response: ApiResponse) => void } export const AuthProvider: React.FC = ({ children }) => { - const [authInfo, setAuthInfo] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + const [authInfo, setAuthInfo] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) - const callApi = ({ apiFn, onSuccess, params, onForbidden, onError }: ApiCallOptions) => { - setError(null); - setLoading(true); - apiFn(params).then((apiResponse: ApiResponse) => { - if (apiResponse.status === 200 && apiResponse.data) { - setError(null); - onSuccess(apiResponse.data); - } else if (apiResponse.status === 204) { - setError(null); - onSuccess(); - } else if (apiResponse.status === 403) { - onForbidden ? onForbidden(apiResponse) : - setError(`${Texts.Error.Communication} ${apiResponse.status}: ${apiResponse.message}`); - } else { - onError ? onError(apiResponse) : - setError(Texts.Auth.NotAvailableAuthInfo); - } - setLoading(false); - }) - } - - useEffect(() => { - if (!authInfo) { - setError(null); - setLoading(true); - callApi({ - apiFn: authInfoApi, - onSuccess: (result?: AuthInfo | Empty) => { - if (result && 'authType' in result) { - setAuthInfo(result); + const callApi = ({ apiFn, onSuccess, params, onForbidden, onError }: ApiCallOptions) => { + setError(null) + setLoading(true) + apiFn(params).then((apiResponse: ApiResponse) => { + if (apiResponse.status === 200 && apiResponse.data) { + setError(null) + onSuccess(apiResponse.data) + } else if (apiResponse.status === 204) { + setError(null) + onSuccess() + } else if (apiResponse.status === 403) { + onForbidden + ? onForbidden(apiResponse) + : setError(`${Texts.Error.Communication} ${apiResponse.status}: ${apiResponse.message}`) + } else { + onError ? onError(apiResponse) : setError(Texts.Auth.NotAvailableAuthInfo) } - }, - }); + setLoading(false) + }) } - }, [authInfo]) - const login = async (username: string, password: string) => { - if (authInfo) { - callApi({ - apiFn: loginApi, - onSuccess: () => { - setAuthInfo({ - ...authInfo, - authenticated: true, - username, - }) - }, - params: { - username: username, password: password - }, - onForbidden: () => setError(Texts.Auth.InvalidUsernameOrPassword), - }); - } else { - setError(Texts.Auth.NotAvailableAuthInfo); + useEffect(() => { + if (!authInfo) { + setError(null) + setLoading(true) + callApi({ + apiFn: authInfoApi, + onSuccess: (result?: AuthInfo | Empty) => { + if (result && 'authType' in result) { + setAuthInfo(result) + } + }, + }) + } + }, [authInfo]) + + const login = async (username: string, password: string) => { + if (authInfo) { + callApi({ + apiFn: loginApi, + onSuccess: () => { + setAuthInfo({ + ...authInfo, + authenticated: true, + username, + }) + }, + params: { + username: username, + password: password, + }, + onForbidden: () => setError(Texts.Auth.InvalidUsernameOrPassword), + }) + } else { + setError(Texts.Auth.NotAvailableAuthInfo) + } } - } - const logout = ({ redirect = false }: LogoutParams) => { - if (redirect) { - window.location.href = "/ui/logout"; - } else { - callApi({ - apiFn: logoutApi, - onSuccess: () => { - setError(null); - setAuthInfo(null); - }, - }); + const logout = ({ redirect = false }: LogoutParams) => { + if (redirect) { + window.location.href = '/ui/logout' + } else { + callApi({ + apiFn: logoutApi, + onSuccess: () => { + setError(null) + setAuthInfo(null) + }, + }) + } } - } - return ( - - {children} - - ); -}; + return {children} +} diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/CodeBlock.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/CodeBlock.tsx index dbe5bd333125..531564a85504 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/CodeBlock.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/CodeBlock.tsx @@ -11,45 +11,49 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Box, useMediaQuery} from '@mui/material'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { materialLight, materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; -import { Theme as ThemeStore, useConfigStore } from "../store"; +import { Box, useMediaQuery } from '@mui/material' +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' +import { materialLight, materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism' +import { Theme as ThemeStore, useConfigStore } from '../store' export interface ICodeBlockProps { - code: string; - language: string; + code: string + language: string } export const CodeBlock = (props: ICodeBlockProps) => { - const config = useConfigStore(); - const { code, language } = props; - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') + const config = useConfigStore() + const { code, language } = props + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') - const styleToUse = () => { - if (config.theme === ThemeStore.Auto) { - return prefersDarkMode ? materialDark : materialLight - } else if (config.theme === ThemeStore.Dark) { - return materialDark - } else { - return materialLight + const styleToUse = () => { + if (config.theme === ThemeStore.Auto) { + return prefersDarkMode ? materialDark : materialLight + } else if (config.theme === ThemeStore.Dark) { + return materialDark + } else { + return materialLight + } } - } - return ( - - - {code} - - - ); + return ( + + + {code} + + + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Dashboard.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Dashboard.tsx index a4b03b6e7c0f..9e6480137f85 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Dashboard.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Dashboard.tsx @@ -11,183 +11,193 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useEffect, useState } from 'react'; -import Typography from '@mui/material/Typography'; -import { - Box, - Grid2 as Grid, -} from "@mui/material"; +import { useEffect, useState } from 'react' +import Typography from '@mui/material/Typography' +import { Box, Grid2 as Grid } from '@mui/material' import { MetricCard } from './MetricCard.tsx' -import { useSnackbar } from "./SnackbarContext.ts"; -import { ApiResponse } from "../api/base.ts"; -import { statsApi, Stats } from "../api/webapp/api.ts"; -import { Texts } from "../constant.ts"; +import { useSnackbar } from './SnackbarContext.ts' +import { ApiResponse } from '../api/base.ts' +import { statsApi, Stats } from '../api/webapp/api.ts' +import { Texts } from '../constant.ts' import { - MAX_HISTORY, - addExponentiallyWeightedToHistory, - addToHistory, - formatCount, - formatDataSizeBytes, - precisionRound, + MAX_HISTORY, + addExponentiallyWeightedToHistory, + addToHistory, + formatCount, + formatDataSizeBytes, + precisionRound, } from '../utils/utils.ts' -interface ClusterStats -{ - runningQueries: number[]; - queuedQueries: number[]; - blockedQueries: number[]; - activeWorkers: number[]; - runningDrivers: number[]; - reservedMemory: number[]; - rowInputRate: number[]; - byteInputRate: number[]; - perWorkerCpuTimeRate: number[]; - - lastRefresh: number | null; - - lastInputRows: number - lastInputBytes: number; - lastCpuTime: number; +interface ClusterStats { + runningQueries: number[] + queuedQueries: number[] + blockedQueries: number[] + activeWorkers: number[] + runningDrivers: number[] + reservedMemory: number[] + rowInputRate: number[] + byteInputRate: number[] + perWorkerCpuTimeRate: number[] + + lastRefresh: number | null + + lastInputRows: number + lastInputBytes: number + lastCpuTime: number } export const Dashboard = () => { - const { showSnackbar } = useSnackbar(); - const initialFilledHistory = Array(MAX_HISTORY).fill(0); - const [clusterStats, setClusterStats] = useState({ - runningQueries: initialFilledHistory, - queuedQueries: initialFilledHistory, - blockedQueries: initialFilledHistory, - activeWorkers: initialFilledHistory, - runningDrivers: initialFilledHistory, - reservedMemory: initialFilledHistory, - rowInputRate: initialFilledHistory, - byteInputRate: initialFilledHistory, - perWorkerCpuTimeRate: initialFilledHistory, - - lastRefresh: null, - - lastInputRows: 0, - lastInputBytes: 0, - lastCpuTime: 0, - }); - const [error, setError] = useState(null); - - useEffect(() => { - getClusterStats(); - const intervalId = setInterval(getClusterStats, 1000); - return () => clearInterval(intervalId); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (error) { - showSnackbar(error, 'error'); - } - }, [error, showSnackbar]) - - const getClusterStats = () => { - setError(null); - statsApi().then((apiResponse: ApiResponse) => { - if (apiResponse.status === 200 && apiResponse.data) { - const newClusterStats: Stats = apiResponse.data; - setClusterStats(prevClusterStats => { - let newRowInputRate: number[] = initialFilledHistory; - let newByteInputRate: number[] = initialFilledHistory; - let newPerWorkerCpuTimeRate: number[] = [] - if (prevClusterStats.lastRefresh !== null) { - const rowsInputSinceRefresh = newClusterStats.totalInputRows - prevClusterStats.lastInputRows - const bytesInputSinceRefresh = newClusterStats.totalInputBytes - prevClusterStats.lastInputBytes - const cpuTimeSinceRefresh = newClusterStats.totalCpuTimeSecs - prevClusterStats.lastCpuTime - const secsSinceRefresh = (Date.now() - prevClusterStats.lastRefresh) / 1000.0 - - newRowInputRate = addExponentiallyWeightedToHistory( - rowsInputSinceRefresh / secsSinceRefresh, - prevClusterStats.rowInputRate - ) - newByteInputRate = addExponentiallyWeightedToHistory( - bytesInputSinceRefresh / secsSinceRefresh, - prevClusterStats.byteInputRate - ) - newPerWorkerCpuTimeRate = addExponentiallyWeightedToHistory( - cpuTimeSinceRefresh / newClusterStats.activeWorkers / secsSinceRefresh, - prevClusterStats.perWorkerCpuTimeRate - ) - } - - return { - // instantaneous stats - runningQueries: addToHistory(newClusterStats.runningQueries, prevClusterStats.runningQueries), - queuedQueries: addToHistory(newClusterStats.queuedQueries, prevClusterStats.queuedQueries), - blockedQueries: addToHistory(newClusterStats.blockedQueries, prevClusterStats.blockedQueries), - activeWorkers: addToHistory(newClusterStats.activeWorkers, prevClusterStats.activeWorkers), - - // moving averages - runningDrivers: addExponentiallyWeightedToHistory( - newClusterStats.runningDrivers, - prevClusterStats.runningDrivers - ), - reservedMemory: addExponentiallyWeightedToHistory( - newClusterStats.reservedMemory, - prevClusterStats.reservedMemory - ), - - // moving averages for diffs - rowInputRate: newRowInputRate, - byteInputRate: newByteInputRate, - perWorkerCpuTimeRate: newPerWorkerCpuTimeRate, - - lastInputRows: newClusterStats.totalInputRows, - lastInputBytes: newClusterStats.totalInputBytes, - lastCpuTime: newClusterStats.totalCpuTimeSecs, - - lastRefresh: Date.now(), - } - }); - setError(null); - } else { - setError(`${Texts.Error.Communication} ${apiResponse.status}: ${apiResponse.message}`); - } + const { showSnackbar } = useSnackbar() + const initialFilledHistory = Array(MAX_HISTORY).fill(0) + const [clusterStats, setClusterStats] = useState({ + runningQueries: initialFilledHistory, + queuedQueries: initialFilledHistory, + blockedQueries: initialFilledHistory, + activeWorkers: initialFilledHistory, + runningDrivers: initialFilledHistory, + reservedMemory: initialFilledHistory, + rowInputRate: initialFilledHistory, + byteInputRate: initialFilledHistory, + perWorkerCpuTimeRate: initialFilledHistory, + + lastRefresh: null, + + lastInputRows: 0, + lastInputBytes: 0, + lastCpuTime: 0, }) - }; - - return ( - <> - - - Cluster Overview - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + const [error, setError] = useState(null) + + useEffect(() => { + getClusterStats() + const intervalId = setInterval(getClusterStats, 1000) + return () => clearInterval(intervalId) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (error) { + showSnackbar(error, 'error') + } + }, [error, showSnackbar]) + + const getClusterStats = () => { + setError(null) + statsApi().then((apiResponse: ApiResponse) => { + if (apiResponse.status === 200 && apiResponse.data) { + const newClusterStats: Stats = apiResponse.data + setClusterStats((prevClusterStats) => { + let newRowInputRate: number[] = initialFilledHistory + let newByteInputRate: number[] = initialFilledHistory + let newPerWorkerCpuTimeRate: number[] = [] + if (prevClusterStats.lastRefresh !== null) { + const rowsInputSinceRefresh = newClusterStats.totalInputRows - prevClusterStats.lastInputRows + const bytesInputSinceRefresh = newClusterStats.totalInputBytes - prevClusterStats.lastInputBytes + const cpuTimeSinceRefresh = newClusterStats.totalCpuTimeSecs - prevClusterStats.lastCpuTime + const secsSinceRefresh = (Date.now() - prevClusterStats.lastRefresh) / 1000.0 + + newRowInputRate = addExponentiallyWeightedToHistory( + rowsInputSinceRefresh / secsSinceRefresh, + prevClusterStats.rowInputRate + ) + newByteInputRate = addExponentiallyWeightedToHistory( + bytesInputSinceRefresh / secsSinceRefresh, + prevClusterStats.byteInputRate + ) + newPerWorkerCpuTimeRate = addExponentiallyWeightedToHistory( + cpuTimeSinceRefresh / newClusterStats.activeWorkers / secsSinceRefresh, + prevClusterStats.perWorkerCpuTimeRate + ) + } + + return { + // instantaneous stats + runningQueries: addToHistory(newClusterStats.runningQueries, prevClusterStats.runningQueries), + queuedQueries: addToHistory(newClusterStats.queuedQueries, prevClusterStats.queuedQueries), + blockedQueries: addToHistory(newClusterStats.blockedQueries, prevClusterStats.blockedQueries), + activeWorkers: addToHistory(newClusterStats.activeWorkers, prevClusterStats.activeWorkers), + + // moving averages + runningDrivers: addExponentiallyWeightedToHistory( + newClusterStats.runningDrivers, + prevClusterStats.runningDrivers + ), + reservedMemory: addExponentiallyWeightedToHistory( + newClusterStats.reservedMemory, + prevClusterStats.reservedMemory + ), + + // moving averages for diffs + rowInputRate: newRowInputRate, + byteInputRate: newByteInputRate, + perWorkerCpuTimeRate: newPerWorkerCpuTimeRate, + + lastInputRows: newClusterStats.totalInputRows, + lastInputBytes: newClusterStats.totalInputBytes, + lastCpuTime: newClusterStats.totalCpuTimeSecs, + + lastRefresh: Date.now(), + } + }) + setError(null) + } else { + setError(`${Texts.Error.Communication} ${apiResponse.status}: ${apiResponse.message}`) + } + }) + } + + return ( + <> + + Cluster Overview + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/DemoComponents.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/DemoComponents.tsx index 44d5566a1ac9..1499dbea0484 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/DemoComponents.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/DemoComponents.tsx @@ -11,119 +11,118 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Typography from '@mui/material/Typography'; +import Typography from '@mui/material/Typography' import { - Box, - Button, - Checkbox, - Grid2 as Grid, - Link, - Paper, - Stack, - Switch, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow -} from "@mui/material"; + Box, + Button, + Checkbox, + Grid2 as Grid, + Link, + Paper, + Stack, + Switch, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, +} from '@mui/material' export const DemoComponents = () => { - const createData = ( - name: string, - calories: number, - fat: number, - carbs: number, - protein: number, - ) => { - return { name, calories, fat, carbs, protein }; - } + const createData = (name: string, calories: number, fat: number, carbs: number, protein: number) => { + return { name, calories, fat, carbs, protein } + } - const rows = [ - createData('Frozen yoghurt', 159, 6.0, 24, 4.0), - createData('Ice cream sandwich', 237, 9.0, 37, 4.3), - createData('Eclair', 262, 16.0, 24, 6.0), - ]; + const rows = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + ] - return ( - <> - - - Demo Components - - - - - Buttons - - - - - - + return ( + <> + + Demo Components + + + + Buttons + + + + + + - - Secondary Button - - - - - + + Secondary Button + + + + + - - Colored Button - - - - - + + Colored Button + + + + + - - Link - Trino Website - + + Link + Trino Website + - - Switch - - + + Switch + + - - Checkbox - - - + + Checkbox + + + - - - - - Dessert (100g serving) - Calories - Fat (g) - Carbs (g) - Protein (g) - - - - {rows.map((row) => ( - - - {row.name} - - {row.calories} - {row.fat} - {row.carbs} - {row.protein} - - ))} - -
-
- - ); + + + + + Dessert (100g serving) + Calories + Fat (g) + Carbs (g) + Protein (g) + + + + {rows.map((row) => ( + + + {row.name} + + {row.calories} + {row.fat} + {row.carbs} + {row.protein} + + ))} + +
+
+ + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Layout.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Layout.tsx index 1388b0c8ef17..cd1fda9b0e56 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Layout.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Layout.tsx @@ -11,87 +11,75 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactNode, useEffect, useState } from 'react'; -import { useLocation, Link } from "react-router-dom"; +import React, { ReactNode, useEffect, useState } from 'react' +import { useLocation, Link } from 'react-router-dom' import { - Avatar, - AppBar, - Box, - Container, - IconButton, - List, - ListItem, - ListItemButton, - ListItemIcon, - ListItemText, - Menu, - MenuItem, - Drawer as MuiDrawer, - Toolbar, - Tooltip, - Typography -} from '@mui/material'; -import { - CSSObject, - Theme, - styled -} from '@mui/material/styles'; -import { - DarkModeOutlined, - LightModeOutlined, - SettingsBrightnessOutlined -} from "@mui/icons-material"; -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import LogoutIcon from '@mui/icons-material/Logout'; -import { Texts } from "../constant"; + Avatar, + AppBar, + Box, + Container, + IconButton, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Drawer as MuiDrawer, + Toolbar, + Tooltip, + Typography, +} from '@mui/material' +import { CSSObject, Theme, styled } from '@mui/material/styles' +import { DarkModeOutlined, LightModeOutlined, SettingsBrightnessOutlined } from '@mui/icons-material' +import ChevronLeftIcon from '@mui/icons-material/ChevronLeft' +import ChevronRightIcon from '@mui/icons-material/ChevronRight' +import LogoutIcon from '@mui/icons-material/Logout' +import { Texts } from '../constant' import trinoLogo from '../assets/trino.svg' -import { - RouterItem, - routers, - routersMapper -} from "../router.tsx"; -import { Theme as ThemeStore, useConfigStore } from "../store"; -import { useAuth } from "./AuthContext"; -import { useSnackbar } from "./SnackbarContext"; +import { RouterItem, routers, routersMapper } from '../router.tsx' +import { Theme as ThemeStore, useConfigStore } from '../store' +import { useAuth } from './AuthContext' +import { useSnackbar } from './SnackbarContext' interface settingsMenuItem { - key: string; - caption: string; - icon?: ReactNode - divider?: boolean; - onClick?: () => void; - disabled?: boolean; + key: string + caption: string + icon?: ReactNode + divider?: boolean + onClick?: () => void + disabled?: boolean } const openedDrawer = (theme: Theme, width: number): CSSObject => ({ - width: width, - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - overflowX: 'hidden', -}); + width: width, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + overflowX: 'hidden', +}) const closedDrawer = (theme: Theme): CSSObject => ({ - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - overflowX: 'hidden', - width: `calc(${theme.spacing(7)} + 1px)`, - [theme.breakpoints.up('sm')]: { - width: `calc(${theme.spacing(8)} + 1px)`, - }, -}); + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + overflowX: 'hidden', + width: `calc(${theme.spacing(7)} + 1px)`, + [theme.breakpoints.up('sm')]: { + width: `calc(${theme.spacing(8)} + 1px)`, + }, +}) const DrawerFooter = styled('div')(({ theme }) => ({ - display: 'flex', - flexGrow: 0, - alignItems: 'center', - justifyContent: 'flex-end', - padding: theme.spacing(0, 1.5), -})); + display: 'flex', + flexGrow: 0, + alignItems: 'center', + justifyContent: 'flex-end', + padding: theme.spacing(0, 1.5), +})) // @ts-expect-error TS2339 const Drawer = styled(MuiDrawer)(({ theme, open, width }) => ({ @@ -100,241 +88,234 @@ const Drawer = styled(MuiDrawer)(({ theme, open, width }) => ({ whiteSpace: 'nowrap', boxSizing: 'border-box', ...(open && { - ...openedDrawer(theme, width), - '& .MuiDrawer-paper': openedDrawer(theme, width), + ...openedDrawer(theme, width), + '& .MuiDrawer-paper': openedDrawer(theme, width), }), ...(!open && { - ...closedDrawer(theme), - '& .MuiDrawer-paper': closedDrawer(theme), + ...closedDrawer(theme), + '& .MuiDrawer-paper': closedDrawer(theme), }), - }), -); +})) -export const RootLayout = (props: { - children: React.ReactNode -}) => { - const config = useConfigStore(); - const { authInfo, logout, error } = useAuth(); - const { showSnackbar } = useSnackbar(); - const location = useLocation(); - const [drawerOpen, setDrawerOpen] = useState(true); - const [selectedDrawerItemKey, setSelectedDrawerItemKey] = useState(location.pathname.substring(location.pathname.lastIndexOf('/') + 1)); - const [anchorElTheme, setAnchorElTheme] = useState(null); - const [anchorElUser, setAnchorElUser] = useState(null); - const username = authInfo?.username || ''; - const userInitials = username?.charAt(0).toUpperCase(); +export const RootLayout = (props: { children: React.ReactNode }) => { + const config = useConfigStore() + const { authInfo, logout, error } = useAuth() + const { showSnackbar } = useSnackbar() + const location = useLocation() + const [drawerOpen, setDrawerOpen] = useState(true) + const [selectedDrawerItemKey, setSelectedDrawerItemKey] = useState( + location.pathname.substring(location.pathname.lastIndexOf('/') + 1) + ) + const [anchorElTheme, setAnchorElTheme] = useState(null) + const [anchorElUser, setAnchorElUser] = useState(null) + const username = authInfo?.username || '' + const userInitials = username?.charAt(0).toUpperCase() - useEffect(() => { - const router = routersMapper[location.pathname]; - if (router && router.itemKey != null && selectedDrawerItemKey !== router.itemKey) { - setSelectedDrawerItemKey(router.itemKey); - } - }, [location, selectedDrawerItemKey]); - - useEffect(() => { - if (error) { - showSnackbar(error, 'error'); - } - }, [error, showSnackbar]) + useEffect(() => { + const router = routersMapper[location.pathname] + if (router && router.itemKey != null && selectedDrawerItemKey !== router.itemKey) { + setSelectedDrawerItemKey(router.itemKey) + } + }, [location, selectedDrawerItemKey]) - const onLogout = () => { - logout({ redirect: authInfo?.authType != 'form', }); - } + useEffect(() => { + if (error) { + showSnackbar(error, 'error') + } + }, [error, showSnackbar]) - const openUserMenu = (event: React.MouseEvent) => { - setAnchorElUser(event.currentTarget); - }; + const onLogout = () => { + logout({ redirect: authInfo?.authType != 'form' }) + } - const closeUserMenu = () => { - setAnchorElUser(null); - } + const openUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget) + } - const openThemeMenu = (event: React.MouseEvent) => { - setAnchorElTheme(event.currentTarget); - } + const closeUserMenu = () => { + setAnchorElUser(null) + } - const closeThemeMenu = () => { - setAnchorElTheme(null); - } + const openThemeMenu = (event: React.MouseEvent) => { + setAnchorElTheme(event.currentTarget) + } - const setTheme = (theme: ThemeStore) => { - config.update((config) => (config.theme = theme)); - } + const closeThemeMenu = () => { + setAnchorElTheme(null) + } - const toggleDrawer = () => { - drawerOpen ? setDrawerOpen(false) : setDrawerOpen(true); - }; + const setTheme = (theme: ThemeStore) => { + config.update((config) => (config.theme = theme)) + } - const settingsMenuItems: settingsMenuItem[] = [ - { - key: 'username', - caption: username, - divider: true, - }, - { - key: "logout", - caption: Texts.Menu.Header.Logout, - onClick: () => { closeUserMenu(); onLogout() }, - icon: , - disabled: authInfo?.authType === 'fixed', - }, - ]; + const toggleDrawer = () => { + drawerOpen ? setDrawerOpen(false) : setDrawerOpen(true) + } - return ( - - theme.zIndex.drawer + 1 }}> - - - - Trino - - - - - {config.theme === ThemeStore.Auto ? ( - - ) : config.theme === ThemeStore.Light ? ( - - ) : config.theme === ThemeStore.Dark ? ( - - ) : null} - - - - {Object.keys(ThemeStore).map((key) => { - const value = ThemeStore[key as keyof typeof ThemeStore]; - return ( - setTheme(value)} - selected={value === config.theme}> - - {value === ThemeStore.Auto ? ( - - ) : value === ThemeStore.Light ? ( - - ) : value === ThemeStore.Dark ? ( - - ) : null} - - {key} - - ) - })} - - - - - - {userInitials} - - - - {settingsMenuItems.map((settingsMenuItem) => ( - - {settingsMenuItem.icon ? - - {settingsMenuItem.icon} - :
} - {settingsMenuItem.caption} + const settingsMenuItems: settingsMenuItem[] = [ + { + key: 'username', + caption: username, + divider: true, + }, + { + key: 'logout', + caption: Texts.Menu.Header.Logout, + onClick: () => { + closeUserMenu() + onLogout() + }, + icon: , + disabled: authInfo?.authType === 'fixed', + }, + ] - - ))} -
-
-
-
- {/*@ts-expect-error TS2322*/} - - - - - {routers.map((routerItem: RouterItem) => ( - - {/*@ts-expect-error TS2769*/} - - - {routerItem.icon} - - - - - ))} - + return ( + + theme.zIndex.drawer + 1 }}> + + + + Trino + + + + + {config.theme === ThemeStore.Auto ? ( + + ) : config.theme === ThemeStore.Light ? ( + + ) : config.theme === ThemeStore.Dark ? ( + + ) : null} + + + + {Object.keys(ThemeStore).map((key) => { + const value = ThemeStore[key as keyof typeof ThemeStore] + return ( + setTheme(value)} + selected={value === config.theme} + > + + {value === ThemeStore.Auto ? ( + + ) : value === ThemeStore.Light ? ( + + ) : value === ThemeStore.Dark ? ( + + ) : null} + + {key} + + ) + })} + + + + + + {userInitials} + + + + {settingsMenuItems.map((settingsMenuItem) => ( + + {settingsMenuItem.icon ? ( + {settingsMenuItem.icon} + ) : ( +
+ )} + {settingsMenuItem.caption} + + ))} +
+
+
+
+ {/*@ts-expect-error TS2322*/} + + + + + {routers.map((routerItem: RouterItem) => ( + + {/*@ts-expect-error TS2769*/} + + {routerItem.icon} + + + + ))} + + + + + {drawerOpen ? : } + + + + + + {props.children} +
- - - {drawerOpen ? : } - - -
- - - - {props.children} - - -
- ); + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Login.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Login.tsx index 7aec474b6686..ead9289b25d6 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Login.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Login.tsx @@ -11,120 +11,127 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react' import { - Box, - Button, - ButtonGroup, - Card, - CardContent, - CircularProgress, - Grid2 as Grid, - TextField, - Typography -} from '@mui/material'; + Box, + Button, + ButtonGroup, + Card, + CardContent, + CircularProgress, + Grid2 as Grid, + TextField, + Typography, +} from '@mui/material' import { useAuth } from './AuthContext' -import trinoLogo from '../assets/trino.svg'; -import { Texts } from '../constant'; -import { useSnackbar } from "./SnackbarContext.ts"; +import trinoLogo from '../assets/trino.svg' +import { Texts } from '../constant' +import { useSnackbar } from './SnackbarContext.ts' export interface ILoginProps { - passwordAllowed: boolean; + passwordAllowed: boolean } interface FormElements extends HTMLFormControlsCollection { - usernameInput: HTMLInputElement - passwordInput: HTMLInputElement + usernameInput: HTMLInputElement + passwordInput: HTMLInputElement } interface LoginFormElement extends HTMLFormElement { - readonly elements: FormElements + readonly elements: FormElements } export const Login = (props: ILoginProps) => { - const { login, loading, error } = useAuth(); - const { showSnackbar } = useSnackbar(); - const { passwordAllowed } = props; + const { login, loading, error } = useAuth() + const { showSnackbar } = useSnackbar() + const { passwordAllowed } = props - const [usernameError, setUsernameError] = useState(false); - const [usernameErrorText, setUsernameErrorText] = useState(""); + const [usernameError, setUsernameError] = useState(false) + const [usernameErrorText, setUsernameErrorText] = useState('') - useEffect(() => { - if (error) { - showSnackbar(error, 'error'); - } - }, [error, showSnackbar]) + useEffect(() => { + if (error) { + showSnackbar(error, 'error') + } + }, [error, showSnackbar]) - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - const { usernameInput, passwordInput } = event.currentTarget.elements; + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault() + const { usernameInput, passwordInput } = event.currentTarget.elements - if (usernameInput.value.length == 0) { - setUsernameError(true); - setUsernameErrorText(Texts.Auth.LoginForm.UsernameMustBeDefined); - } - else { - setUsernameError(false); - setUsernameErrorText(""); - login(usernameInput.value, passwordInput.value); + if (usernameInput.value.length == 0) { + setUsernameError(true) + setUsernameErrorText(Texts.Auth.LoginForm.UsernameMustBeDefined) + } else { + setUsernameError(false) + setUsernameErrorText('') + login(usernameInput.value, passwordInput.value) + } } - } - const formDisabled = loading; + const formDisabled = loading - return ( - - -
- - - - - - {Texts.Auth.LoginForm.LogIn} - - - - - - - - - - - {loading ? - - - - - - {Texts.Auth.LoginForm.LoggingIn} - - :
- } - - - - - - - - - ); + return ( + + +
+ + + + + + {Texts.Auth.LoginForm.LogIn} + + + + + + + + + + + {loading ? ( + + + + + + {Texts.Auth.LoginForm.LoggingIn} + + + ) : ( +
+ )} + + + + + + + + + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/MetricCard.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/MetricCard.tsx index add6024b5a05..c0fa342401b5 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/MetricCard.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/MetricCard.tsx @@ -11,66 +11,66 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import Typography from '@mui/material/Typography'; -import { Grid2 as Grid } from "@mui/material"; -import { SparkLineChart } from '@mui/x-charts/SparkLineChart'; +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import Typography from '@mui/material/Typography' +import { Grid2 as Grid } from '@mui/material' +import { SparkLineChart } from '@mui/x-charts/SparkLineChart' interface IMetricCardProps { - title: string; - values: number[]; - numberFormatter?: (n: number | null) => string; + title: string + values: number[] + numberFormatter?: (n: number | null) => string } export const MetricCard = (props: IMetricCardProps) => { - const { title, values, numberFormatter } = props; - const lastValue = values[values.length - 1]; + const { title, values, numberFormatter } = props + const lastValue = values[values.length - 1] - return ( - - - - {title} - - - - - - {numberFormatter ? numberFormatter(lastValue) : lastValue} - - - - - - - - - - - - ) + return ( + + + + {title} + + + + + + {numberFormatter ? numberFormatter(lastValue) : lastValue} + + + + + + + + + + + + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarContext.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarContext.ts index 943796b9586b..d7f574620edb 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarContext.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarContext.ts @@ -11,19 +11,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createContext, useContext } from 'react'; -import { SnackbarSeverity } from "./SnackbarProvider"; +import { createContext, useContext } from 'react' +import { SnackbarSeverity } from './SnackbarProvider' interface SnackbarContextProps { - showSnackbar: (message: string, severity?: SnackbarSeverity) => void; + showSnackbar: (message: string, severity?: SnackbarSeverity) => void } -export const SnackbarContext = createContext(undefined); +export const SnackbarContext = createContext(undefined) export const useSnackbar = (): SnackbarContextProps => { - const context = useContext(SnackbarContext); - if (context === undefined) { - throw new Error('useSnackbar must be used within a SnackbarProvider'); - } - return context; -}; + const context = useContext(SnackbarContext) + if (context === undefined) { + throw new Error('useSnackbar must be used within a SnackbarProvider') + } + return context +} diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarProvider.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarProvider.tsx index 482d82c1af2d..0ee331256ca3 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarProvider.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/SnackbarProvider.tsx @@ -11,50 +11,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactNode, useCallback, useState } from "react"; -import Snackbar from "@mui/material/Snackbar"; -import MuiAlert from "@mui/material/Alert"; -import { SnackbarContext } from "./SnackbarContext"; +import React, { ReactNode, useCallback, useState } from 'react' +import Snackbar from '@mui/material/Snackbar' +import MuiAlert from '@mui/material/Alert' +import { SnackbarContext } from './SnackbarContext' -export type SnackbarSeverity = 'error' | 'warning' | 'info' | 'success'; +export type SnackbarSeverity = 'error' | 'warning' | 'info' | 'success' interface SnackbarState { - open: boolean; - message: string; - severity: SnackbarSeverity; + open: boolean + message: string + severity: SnackbarSeverity } export const SnackbarProvider: React.FC<{ children: ReactNode }> = ({ children }) => { - const [snackbar, setSnackbar] = useState({ - open: false, - message: '', - severity: 'info', - }); + const [snackbar, setSnackbar] = useState({ + open: false, + message: '', + severity: 'info', + }) - const showSnackbar = useCallback((message: string, severity: SnackbarSeverity = 'error') => { - setSnackbar({ open: true, message, severity }); - }, []); + const showSnackbar = useCallback((message: string, severity: SnackbarSeverity = 'error') => { + setSnackbar({ open: true, message, severity }) + }, []) - const handleClose = (_event?: React.SyntheticEvent | Event, reason?: string) => { - if (reason === 'clickaway') { - return; + const handleClose = (_event?: React.SyntheticEvent | Event, reason?: string) => { + if (reason === 'clickaway') { + return + } + setSnackbar((prev) => ({ ...prev, open: false })) } - setSnackbar((prev) => ({ ...prev, open: false })); - }; - return ( - - {children} - - - {snackbar.message} - - - - ); -}; + return ( + + {children} + + + {snackbar.message} + + + + ) +} diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Workers.tsx b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Workers.tsx index 57feec5f7a50..2aa1d884e19a 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Workers.tsx +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/components/Workers.tsx @@ -11,22 +11,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Box, - Typography -} from "@mui/material"; +import { Box, Typography } from '@mui/material' export const Workers = () => { - return ( - <> - - - Workers - - - - Placeholder for Workers - - - ); + return ( + <> + + Workers + + Placeholder for Workers + + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/store/config.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/store/config.ts index 9a9528610088..67ce3ea606e0 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/store/config.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/store/config.ts @@ -11,55 +11,55 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { create } from "zustand"; -import { persist } from "zustand/middleware"; -import { StoreKey } from "../constant"; +import { create } from 'zustand' +import { persist } from 'zustand/middleware' +import { StoreKey } from '../constant' export enum Theme { - Auto = "auto", - Dark = "dark", - Light = "light", + Auto = 'auto', + Dark = 'dark', + Light = 'light', } export const DEFAULT_CONFIG = { - avatar: "", - theme: Theme.Auto as Theme, -}; + avatar: '', + theme: Theme.Auto as Theme, +} -export type AppConfig = typeof DEFAULT_CONFIG; +export type AppConfig = typeof DEFAULT_CONFIG export type AppConfigStore = AppConfig & { - reset: () => void; - update: (updater: (config: AppConfig) => void) => void; -}; + reset: () => void + update: (updater: (config: AppConfig) => void) => void +} export const useConfigStore = create()( persist( (set, get) => ({ - ...DEFAULT_CONFIG, + ...DEFAULT_CONFIG, - reset() { - set(() => ({ ...DEFAULT_CONFIG })); - }, + reset() { + set(() => ({ ...DEFAULT_CONFIG })) + }, - update(updater) { - const config = { ...get() }; - updater(config); - set(() => config); - }, + update(updater) { + const config = { ...get() } + updater(config) + set(() => config) + }, }), { - name: StoreKey.Config, - version: 1, - migrate(persistedState, version) { - const state = persistedState as AppConfig; + name: StoreKey.Config, + version: 1, + migrate(persistedState, version) { + const state = persistedState as AppConfig - if (version < 1) { - // merge your old config - } - /* eslint-disable @typescript-eslint/no-explicit-any */ - return state as any; - }, - }, - ), -); + if (version < 1) { + // merge your old config + } + /* eslint-disable @typescript-eslint/no-explicit-any */ + return state as any + }, + } + ) +) diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/store/index.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/store/index.ts index d8e05ad8116f..f1cad38b3052 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/store/index.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/store/index.ts @@ -11,4 +11,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from "./config"; +export * from './config' diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/merge.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/merge.ts index faeb1455cc4d..414b365bf51c 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/merge.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/merge.ts @@ -13,11 +13,11 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ export function merge(target: any, source: any): any { - Object.keys(source).forEach(function (key) { - if (source[key] && typeof source[key] === "object") { - merge((target[key] = target[key] || {}), source[key]); - return; - } - target[key] = source[key]; - }); + Object.keys(source).forEach(function (key) { + if (source[key] && typeof source[key] === 'object') { + merge((target[key] = target[key] || {}), source[key]) + return + } + target[key] = source[key] + }) } diff --git a/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/utils.ts b/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/utils.ts index aca28bdbeed4..2f59a3df82f3 100644 --- a/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/utils.ts +++ b/core/trino-web-ui/src/main/resources/webapp-preview/src/utils/utils.ts @@ -20,107 +20,107 @@ export const MAX_HISTORY = 60 * 5 const MOVING_AVERAGE_ALPHA = 0.2 export function addToHistory(value: number, valuesArray: number[]): number[] { - if (valuesArray.length === 0) { - return valuesArray.concat([value]) - } - return valuesArray.concat([value]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)) + if (valuesArray.length === 0) { + return valuesArray.concat([value]) + } + return valuesArray.concat([value]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)) } export function addExponentiallyWeightedToHistory(value: number, valuesArray: number[]): number[] { - if (valuesArray.length === 0) { - return valuesArray.concat([value]) - } + if (valuesArray.length === 0) { + return valuesArray.concat([value]) + } - let movingAverage = value * MOVING_AVERAGE_ALPHA + valuesArray[valuesArray.length - 1] * (1 - MOVING_AVERAGE_ALPHA) - if (value < 1) { - movingAverage = 0 - } + let movingAverage = value * MOVING_AVERAGE_ALPHA + valuesArray[valuesArray.length - 1] * (1 - MOVING_AVERAGE_ALPHA) + if (value < 1) { + movingAverage = 0 + } - return valuesArray.concat([movingAverage]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)) + return valuesArray.concat([movingAverage]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)) } export function precisionRound(n: number | null): string { - if (n === null) { - return ''; - } + if (n === null) { + return '' + } - if (n === undefined) { - return 'n/a' - } - if (n < 10) { - return n.toFixed(2) - } - if (n < 100) { - return n.toFixed(1) - } - return Math.round(n).toString() + if (n === undefined) { + return 'n/a' + } + if (n < 10) { + return n.toFixed(2) + } + if (n < 100) { + return n.toFixed(1) + } + return Math.round(n).toString() } export function formatCount(count: number | null): string { - if (count === null) { - return ''; - } + if (count === null) { + return '' + } - let unit = '' - if (count > 1000) { - count /= 1000 - unit = 'K' - } - if (count > 1000) { - count /= 1000 - unit = 'M' - } - if (count > 1000) { - count /= 1000 - unit = 'B' - } - if (count > 1000) { - count /= 1000 - unit = 'T' - } - if (count > 1000) { - count /= 1000 - unit = 'Q' - } - return precisionRound(count) + unit + let unit = '' + if (count > 1000) { + count /= 1000 + unit = 'K' + } + if (count > 1000) { + count /= 1000 + unit = 'M' + } + if (count > 1000) { + count /= 1000 + unit = 'B' + } + if (count > 1000) { + count /= 1000 + unit = 'T' + } + if (count > 1000) { + count /= 1000 + unit = 'Q' + } + return precisionRound(count) + unit } export function formatDataSizeBytes(size: number | null): string { - return formatDataSizeMinUnit(size, '') + return formatDataSizeMinUnit(size, '') } export function formatDataSize(size: number): string { - return formatDataSizeMinUnit(size, 'B') + return formatDataSizeMinUnit(size, 'B') } function formatDataSizeMinUnit(size: number | null, minUnit: string): string { - if (size === null) { - return ''; - } + if (size === null) { + return '' + } - let unit = minUnit - if (size === 0) { - return '0' + unit - } - if (size >= 1024) { - size /= 1024 - unit = 'K' + minUnit - } - if (size >= 1024) { - size /= 1024 - unit = 'M' + minUnit - } - if (size >= 1024) { - size /= 1024 - unit = 'G' + minUnit - } - if (size >= 1024) { - size /= 1024 - unit = 'T' + minUnit - } - if (size >= 1024) { - size /= 1024 - unit = 'P' + minUnit - } - return precisionRound(size) + unit + let unit = minUnit + if (size === 0) { + return '0' + unit + } + if (size >= 1024) { + size /= 1024 + unit = 'K' + minUnit + } + if (size >= 1024) { + size /= 1024 + unit = 'M' + minUnit + } + if (size >= 1024) { + size /= 1024 + unit = 'G' + minUnit + } + if (size >= 1024) { + size /= 1024 + unit = 'T' + minUnit + } + if (size >= 1024) { + size /= 1024 + unit = 'P' + minUnit + } + return precisionRound(size) + unit }