diff --git a/package-lock.json b/package-lock.json index 67374e96..c24d5b02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "env-cmd": "^10.1.0", "jquery": "^3.6.0", "lodash": "^4.17.21", + "memoize-one": "^6.0.0", "moment": "^2.29.2", "nanoid": "^3.3.1", "prismjs": "^1.27.0", @@ -13790,6 +13791,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -30295,6 +30301,11 @@ "fs-monkey": "1.0.3" } }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", diff --git a/package.json b/package.json index baf037fd..6290d7b0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "env-cmd": "^10.1.0", "jquery": "^3.6.0", "lodash": "^4.17.21", + "memoize-one": "^6.0.0", "moment": "^2.29.2", "nanoid": "^3.3.1", "prismjs": "^1.27.0", diff --git a/src/actions/index.js b/src/actions/index.js index bf067bae..2380f243 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -6,6 +6,8 @@ export * from "./setRangeOpen"; export * from "./setTimeRangeLabel"; export * from "./setApiUrl"; export * from "./setQuery"; +export * from "./setQueryTime"; +export * from "./setQueryType"; export * from "./setIsSubmit"; export * from "./setMatrixData"; export * from "./setQueryHistory"; @@ -18,3 +20,4 @@ export * from "./removeAlert"; export * from "./setFromTime"; export * from "./setToTime"; export * from "./setTheme"; +export * from "./setIsEmptyView"; \ No newline at end of file diff --git a/src/actions/loadLabels.js b/src/actions/loadLabels.js index 8649f907..72ef9049 100644 --- a/src/actions/loadLabels.js +++ b/src/actions/loadLabels.js @@ -4,6 +4,7 @@ import setLoading from "./setLoading"; import { setApiError } from "./setApiError"; import { createAlert } from "./createAlert"; + export default function loadLabels(apiUrl) { const origin = window.location.origin; const url = apiUrl; @@ -36,6 +37,7 @@ export default function loadLabels(apiUrl) { values: [], })); if (labels) { + console.log("loding labels from loadLabels") dispatch(setLabels(labels || [])); dispatch(setApiError("")); dispatch(setLoading(false)); @@ -48,6 +50,7 @@ export default function loadLabels(apiUrl) { dispatch(setApiError("")); dispatch(setLabels([])); + } }) .catch((error) => { diff --git a/src/actions/loadLogs.js b/src/actions/loadLogs.js index 58051e05..c706df31 100644 --- a/src/actions/loadLogs.js +++ b/src/actions/loadLogs.js @@ -6,7 +6,55 @@ import setMatrixData from "./setMatrixData"; import { nanoid } from "nanoid"; import { setStartTime, setStopTime } from "./"; import { findRangeByLabel } from "../components/StatusBar/components/daterangepicker/utils"; +import { setQueryTime } from "./setQueryTime"; +import setIsEmptyView from "./setIsEmptyView"; + +// import adjustedStep from "../components/QueryTypeBar/helpers"; +const debugMode = store.getState().debugMode +export async function getAsyncResponse( + cb //: callback dispatch function +) { + return await cb; +} + +export function sortMessagesByTimestamp( + messages //:array sort by timestamp +) { + const startTime = performance.now() + const mess = messages?.sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)); + const duration = performance.now() - startTime; + if(debugMode) console.log( "🚧 loadLogs / sorting logs took: ",duration," ms") + return mess + +} +export function fromNanoSec( + ts // :timestamp +) { + return parseInt(ts / 1000000); +} + +export function mapStreams (streams) { + const startTime = performance.now() + let messages = [] + + streams.forEach((stream) => { + stream.values.forEach(([ts,text], i) => { + messages.push({ + type:'stream', + timestamp:fromNanoSec(ts), + text, + tags: stream.stream || {}, + showTs: true, + showLabels: false, + id: nanoid(), + }); + }); + }); + const duration = performance.now() - startTime; + if(debugMode) console.log( "🚧 loadLogs / mapping logs took: ",duration," ms") + return messages +}; export default function loadLogs() { const localStore = store.getState(); @@ -18,37 +66,40 @@ export default function loadLogs() { label: rangeLabel, from, to, + debugMode, } = localStore; let { start: startTs, stop: stopTs } = localStore; - function adjustForTimezone(date){ - - var timeOffsetInMS = date.getTimezoneOffset() * 60000; - date.setTime(date.getTime() + timeOffsetInMS); - return date - } - function getTimeParsed(time) { return time.getTime() + "000000"; } - const timeZone = new Date().getTimezoneOffset() + const time = localStore.time || new Date().getTime() + "000000"; const parsedStart = getTimeParsed(startTs); const parsedStop = getTimeParsed(stopTs); const parsedTime = "&start=" + (from || parsedStart) + "&end=" + (to || parsedStop); + if (findRangeByLabel(rangeLabel)) { - ({ dateStart: startTs, dateEnd: stopTs } = - findRangeByLabel(rangeLabel)); + ({ dateStart: startTs, dateEnd: stopTs } = findRangeByLabel( + rangeLabel + )); + } store.dispatch(setStartTime(startTs)); store.dispatch(setStopTime(stopTs)); - + const queryType = store.getState().queryType; const origin = window.location.origin; const url = apiUrl; + const queryStep = `&step=${step || 120}`; const encodedQuery = `${encodeURIComponent(query)}`; - const getUrl = `${url}/loki/api/v1/query_range?query=${encodedQuery}&limit=${limit}${parsedTime}${queryStep}`; + const queryUrl = `${url}/loki/api/v1`; + + const rangeEP = `${queryUrl}/query_range?query=${encodedQuery}&limit=${limit}${parsedTime}${queryStep}`; + const instantEP = `${queryUrl}/query?query=${encodedQuery}&limit=${limit}&time=${time}`; + + const endpoint = { instant: instantEP, range: rangeEP }; const options = { method: "GET", @@ -58,84 +109,95 @@ export default function loadLogs() { }, }; - const fromNanoSec = (ts) => parseInt(ts / 1000000); - const toMiliseC = (ts) => parseInt(ts * 1000); - const getTimestamp = (type) => (ts) => - type === "streams" - ? fromNanoSec(ts) - : type === "matrix" - ? toMiliseC(ts) - : ts; - const mapStreams = (streams, messages, type) => { - streams.forEach((stream) => { - stream.values.forEach((log, i) => { - let [ts, text] = log; - messages.push({ - type, - timestamp: getTimestamp(type)(ts), - text, - tags: - type === "streams" - ? stream.stream - : type === "matrix" - ? stream.metric - : {}, - showTs: true, - showLabels: false, - id: nanoid(), - }); - }); - }); - }; - - //const mapMatrix return async function (dispatch) { dispatch(setLoading(true)); + dispatch(setIsEmptyView(false)); dispatch(setLogs([])); dispatch(setMatrixData([])); await axios - .get(getUrl, options) + .get(endpoint[queryType], options) ?.then((response) => { + if (response?.data?.streams?.length === 0) { + if (debugMode) + console.log( + "🚧 loadLogs / getting no data from streams" + ); + dispatch(setIsEmptyView(true)); + } if (response?.data?.data) { let messages = []; - const result = response?.data?.data?.result; // array + const result = response?.data?.data?.result; const type = response?.data?.data?.resultType; if (type === "streams") { - mapStreams(result, messages, type); + messages = mapStreams(result); dispatch(setMatrixData([])); - const messSorted = messages?.sort((a, b) => - a.timestamp < b.timestamp ? 1 : -1 - ); + const messSorted = sortMessagesByTimestamp(messages) if (messSorted) { - dispatch(setLogs(messSorted || [])); - - dispatch(setLoading(false)); + try { + getAsyncResponse( + dispatch(setLogs(messSorted || [])) + ).then(() => { + if (messSorted.length === 0) { + if (debugMode) + console.log( + "🚧 loadLogs / getting no messages sorted" + ); + dispatch(setIsEmptyView(true)); + } + dispatch(setIsEmptyView(false)); + dispatch(setLoading(false)); + }); + if (queryType === "instant") { + store.dispatch(setQueryTime(time)); + } + } catch (e) { + console.log(e); + } } } if (type === "matrix") { - const idResult = - result?.map((m) => ({ ...m, id: nanoid() })) || []; - dispatch(setMatrixData(idResult || [])); - dispatch(setLoading(false)); + try { + const idResult = + result?.map((m) => ({ ...m, id: nanoid() })) || + []; + + getAsyncResponse( + dispatch(setMatrixData(idResult || [])) + ).then(() => { + dispatch(setLoading(false)); + if (idResult.length === 0) { + if (debugMode) + console.log( + "🚧 loadLogs / getting no data from matrix" + ); + dispatch(setIsEmptyView(true)); + } + dispatch(setIsEmptyView(false)); + }); + } catch (e) { + if (debugMode) + console.log( + "🚧 loadLogs / getting an error from rendering matrix type streams" + ); + console.log(e); + } } - // dispatch(setLoading(false)); } else { dispatch(setLogs([])); dispatch(setMatrixData([])); - dispatch(setLoading(false)); + + // } - // dispatch(setLoading(false)); }) .catch((error) => { - dispatch(setLogs([])); dispatch(setMatrixData([])); - dispatch(setLoading(false)); - - + if (debugMode) + console.log("getting an error from response: ", error); + dispatch(setIsEmptyView(true)); }); }; } diff --git a/src/actions/setFromTime.js b/src/actions/setFromTime.js index adfec8dc..1ecccc78 100644 --- a/src/actions/setFromTime.js +++ b/src/actions/setFromTime.js @@ -1,7 +1,8 @@ -export default function setFromTime(toTime){ - return function (dispatch){ - dispatch({ - type:"SET_FROM_TIME" - }) - } +export default function setFromTime(toTime){ + return function (dispatch){ + dispatch({ + type:"SET_FROM_TIME", + toTime + }) + } } \ No newline at end of file diff --git a/src/actions/setIsEmptyView.js b/src/actions/setIsEmptyView.js new file mode 100644 index 00000000..0b2c461e --- /dev/null +++ b/src/actions/setIsEmptyView.js @@ -0,0 +1,8 @@ +const setIsEmptyView = (isEmptyView) => (dispatch) => { + dispatch({ + type: 'SET_IS_EMPTY_VIEW', + isEmptyView, + }) +} + +export default setIsEmptyView; \ No newline at end of file diff --git a/src/actions/setLabelValues.js b/src/actions/setLabelValues.js index 4e82dd8d..98ab8da2 100644 --- a/src/actions/setLabelValues.js +++ b/src/actions/setLabelValues.js @@ -1,7 +1,7 @@ -const setLabelValues = (labelValues) => (dispatch) => { - dispatch({ - type: 'SET_LABEL_VALUES', - labelValues - }); -} -export default setLabelValues; +const setLabelValues = (labelValues) => (dispatch) => { + dispatch({ + type: 'SET_LABEL_VALUES', + labelValues + }); +} +export default setLabelValues; diff --git a/src/actions/setLabels.js b/src/actions/setLabels.js index 0c8a8437..c97659f3 100644 --- a/src/actions/setLabels.js +++ b/src/actions/setLabels.js @@ -1,6 +1,6 @@ -export const setLabels = (labels) => (dispatch) => { - dispatch({ - type: 'SET_LABELS', - labels: labels - }); -}; +export const setLabels = (labels) => (dispatch) => { + dispatch({ + type: 'SET_LABELS', + labels: labels + }); +}; diff --git a/src/actions/setQueryLimit.js b/src/actions/setQueryLimit.js index e8af27d4..b05e042e 100644 --- a/src/actions/setQueryLimit.js +++ b/src/actions/setQueryLimit.js @@ -1,6 +1,6 @@ -export const setQueryLimit = (limit) => (dispatch) => { - dispatch({ - type: 'SET_QUERY_LIMIT', - limit - }); -} +export const setQueryLimit = (limit) => (dispatch) => { + dispatch({ + type: 'SET_QUERY_LIMIT', + limit + }); +} diff --git a/src/actions/setQueryTime.js b/src/actions/setQueryTime.js new file mode 100644 index 00000000..7f77f45a --- /dev/null +++ b/src/actions/setQueryTime.js @@ -0,0 +1,6 @@ +export const setQueryTime = (time) => (dispatch)=>{ + dispatch({ + type: 'SET_QUERY_TIME', + time + }) +} diff --git a/src/actions/setQueryType.js b/src/actions/setQueryType.js new file mode 100644 index 00000000..22b71896 --- /dev/null +++ b/src/actions/setQueryType.js @@ -0,0 +1,6 @@ +export const setQueryType = (queryType) => (dispatch) => { + dispatch({ + type: "SET_QUERY_TYPE", + queryType, + }); +}; diff --git a/src/components/DataView/DataView.js b/src/components/DataView/DataView.js index 85ef1e44..12e05cb0 100644 --- a/src/components/DataView/DataView.js +++ b/src/components/DataView/DataView.js @@ -1,28 +1,30 @@ import React, { Component } from "react"; import { connect } from "react-redux"; - import { DataViewCont, DataViewStyled, Loader } from "./styled"; - import ClokiChart from "../../plugins/charts"; import QueryHistory from "../../plugins/queryhistory"; -import LogsRow from "./LogsRow"; import EmptyView from "./EmptyView"; import { ThemeProvider } from "@emotion/react"; import { themes } from "../../theme/themes"; +import { LogRows } from "./LogRows"; class DataView extends Component { constructor(props) { super(props); + const { messages } = props || []; this.state = { limit: props.limit || 100, - messages: props.messages || [], + messages, matrixData: props.matrixData || [], loading: false, theme: props.theme, + isEmptyView: props.isEmptyView || false, }; } + getMatrixForChart = () => { return this.props.matrixData; }; + getLimit = () => { return this.props.limit; }; @@ -32,28 +34,21 @@ class DataView extends Component { - {this.props.messages.length > 0 && - this.getMatrixForChart().length < 1 - ? this.props.messages.map((message, key) => ( - - )) - : null} - + {this.props.messages.length > 0 && ( + + )} {this.getMatrixForChart().length > 0 ? ( ) : null} - {this.props.messages.length < 1 && - this.getMatrixForChart().length < 1 && - !this.props.loading && } - + {this.props.loading && } + + {this.props.isEmptyView && } + + @@ -64,12 +59,11 @@ class DataView extends Component { const mapStateToProps = (state) => { return { messages: state.logs, - start: state.start, - stop: state.stop, limit: state.limit, loading: state.loading, matrixData: state.matrixData, theme: state.theme, + isEmptyView: state.isEmptyView, }; }; diff --git a/src/components/DataView/EmptyView.js b/src/components/DataView/EmptyView.js index 0d91c751..45d79f93 100644 --- a/src/components/DataView/EmptyView.js +++ b/src/components/DataView/EmptyView.js @@ -1,17 +1,24 @@ import { ThemeProvider } from "@emotion/react"; + import { useSelector } from "react-redux"; import { themes } from "../../theme/themes"; import { EmptyViewContainer } from "./styled"; export default function EmptyView() { - const theme = useSelector( store => store.theme) + const theme = useSelector((store) => store.theme); + const loading = useSelector((store) => store.loading); + + return ( - - - { - "Please adjust search parameters and click on ‘Show Logs’ button" - } - - - ); + !loading && ( + + + { + "Please adjust search parameters and click on ‘Show Logs’ button" + } + + + ) + ) + } diff --git a/src/components/DataView/LogRows/index.js b/src/components/DataView/LogRows/index.js new file mode 100644 index 00000000..05e8e586 --- /dev/null +++ b/src/components/DataView/LogRows/index.js @@ -0,0 +1,80 @@ +import memoize from "memoize-one"; +import { PureComponent } from "react"; +import { formatDate, getRowColor } from "../helpers"; +import { LogRow, RowLogContent, RowTimestamp } from "../styled"; +import ValueTags from "../ValueTags"; + +function Row({ toggleItemActive, index, log }) { + + return ( + { + toggleItemActive(index); + }} + > +
+ {formatDate(log.timestamp)} + {log.text} +
+ + {log.showLabels && ( +
+ +
+ )} +
+ ) + +} + +const createItemData = memoize((items, toggleItemActive) => ({ + items, + toggleItemActive, +})); + +function Logs({ items, toggleItemActive }) { + const itemData = createItemData(items, toggleItemActive); + return ( + itemData && + itemData.items.map((log, key) => ( + + )) + ); +} + +export class LogRows extends PureComponent { + constructor(props) { + super(props); + const { messages } = props || []; + + this.state = { + messages, + }; + } + + toggleItemActive = (index) => + this.setState((prevState) => { + const message = prevState.messages[index]; + const messages = prevState.messages.concat(); + messages[index] = { + ...message, + showLabels: !message.showLabels, + }; + return { messages }; + }); + + render() { + return ( + + ); + } +} diff --git a/src/components/DataView/LogsRow.js b/src/components/DataView/LogsRow.js deleted file mode 100644 index 2db79750..00000000 --- a/src/components/DataView/LogsRow.js +++ /dev/null @@ -1,44 +0,0 @@ -import { formatDate, getRowColor, toggleActiveStyles } from "./helpers"; -import { LogRow, RowLogContent, RowTimestamp } from "./styled"; -import ValueTags from "./ValueTags"; -import { useSelector, useDispatch, useStore } from "react-redux"; -import setLogs from "../../actions/setLogs"; -import { ThemeProvider } from "@emotion/react"; -import { themes } from "../../theme/themes"; - -export default function LogsRow({ message }) { - const dispatch = useDispatch(); - const messages = useSelector((store) => store.logs); - - const theme = useStore().getState().theme; - function toggleTagsActive(idx) { - let arrCopy = [...messages]; - arrCopy.forEach((entry) => { - if (entry.id === idx) { - entry.showLabels = entry.showLabels ? false : true; - } - }); - dispatch(setLogs(arrCopy)); - } - - return ( - - { - toggleTagsActive(message.id); - }} - > -
- {formatDate(message.timestamp)} - {message.text} -
- {message.tags && ( -
- -
- )} -
-
- ); -} diff --git a/src/components/DataView/ValueTags.js b/src/components/DataView/ValueTags.js index 3eb244e1..5bbe0ef2 100644 --- a/src/components/DataView/ValueTags.js +++ b/src/components/DataView/ValueTags.js @@ -7,23 +7,38 @@ import { ZoomIn, ZoomOut } from "@mui/icons-material/"; import { useSelector } from "react-redux"; import { themes } from "../../theme/themes"; import { ThemeProvider } from "@emotion/react"; -import styled from '@emotion/styled' +import styled from "@emotion/styled"; const ValueTagsStyled = styled.div` - color: ${props => props.theme.textPrimary}; - flex:1; + color: ${(props) => props.theme.textPrimary}; + flex: 1; + z-index: 10000; + display: flex; &:hover { - background: ${props => props.theme.widgetContainer}; + background: ${(props) => props.theme.widgetContainer}; } -` +`; + export default function ValueTags({ tags }) { const theme = useSelector((store) => store.theme); const isEmbed = useSelector((store) => store.isEmbed); + const query = useSelector((store) => store.query); async function addLabel(e, key, value, isInverted = false) { e.preventDefault(); e.stopPropagation(); const { labels, apiUrl } = store.getState(); const label = labels.find((label) => label.name === key); + const symb = isInverted ? "!=" : "="; + const isAlreadySelected = query.includes(`${key}="${value}"`); + const isAlreadyInverted = query.includes(`${key}!="${value}"`); + + if ( + (isAlreadyInverted && isInverted) || + (isAlreadySelected && !isInverted) + ) { + return; + } + if (label) { const labelValue = label.values.find((tag) => tag.name === value); if (labelValue?.selected && labelValue.inverted === isInverted) { @@ -35,12 +50,13 @@ export default function ValueTags({ tags }) { labelValue.inverted = !labelValue.inverted && isInverted; label.selected = label.values.some((value) => value.selected); store.dispatch(setLabels(labels)); + } else { await store.dispatch(loadLabelValues(label, labels, apiUrl)); const updatedLabels = store.getState().labels; const updatedLabel = updatedLabels.find( (label) => label.name === key - ); + ); const labelValue = updatedLabel.values.find( (tag) => tag.name === value ); @@ -50,44 +66,61 @@ export default function ValueTags({ tags }) { updatedLabel.selected = updatedLabel.values.some( (value) => value.selected ); + console.log('labels set if no labelvalue') store.dispatch(setLabels(updatedLabels)); } queryBuilderWithLabels(); + store.dispatch(loadLogs()); + } else { + queryBuilderWithLabels(true, [`${key}${symb}"${value}"`]); store.dispatch(loadLogs()); } } return ( - {Object.entries(tags).map(([key, value], k) => ( -
- {!isEmbed && ( - <> - addLabel(e, key, value)} - className={"icon"} - > - - - addLabel(e, key, value, true)} - className={"icon"} - > - - - - )} +
+ {!isEmbed && ( + <> + addLabel(e, key, value)} + className={"icon"} + > + + + + addLabel(e, key, value, true) + } + className={"icon"} + > + + + + )} - {key} - {value} -
+ {key} + {value} +
))}
diff --git a/src/components/DataView/helpers/index.js b/src/components/DataView/helpers/index.js index d70e87f4..a67f2107 100644 --- a/src/components/DataView/helpers/index.js +++ b/src/components/DataView/helpers/index.js @@ -15,6 +15,7 @@ export function getRowColor(tags) { } export function toggleActiveStyles(idx) { + console.log(idx, 'toggled') return idx.showLabels ? "value-tags-container labelsActive" : "value-tags-container labelsInactive"; diff --git a/src/components/LabelBrowser/QueryBar.js b/src/components/LabelBrowser/QueryBar.js index 55bea3f5..a9090697 100644 --- a/src/components/LabelBrowser/QueryBar.js +++ b/src/components/LabelBrowser/QueryBar.js @@ -25,6 +25,7 @@ import debugLog from "./helpers/debugLog"; import { ThemeProvider } from "@emotion/react"; import { themes } from "../../theme/themes"; import { sendLabels } from "../../hooks/useLabels"; +import QueryTypeBar from "../QueryTypeBar"; export const QueryBar = () => { const dispatch = useDispatch(); @@ -66,9 +67,6 @@ export const QueryBar = () => { const onValueDisplay = (e) => { e.preventDefault(); const isOpen = labelsBrowserOpen ? false : true; - if (isOpen) { - dispatch(loadLabels(apiUrl)); - } dispatch(setLabelsBrowserOpen(isOpen)); }; @@ -85,7 +83,6 @@ export const QueryBar = () => { onSubmit(e); } }; - const onSubmit = (e) => { e.preventDefault(); @@ -171,6 +168,7 @@ export const QueryBar = () => { isMobile={false} /> + ) diff --git a/src/components/LabelBrowser/ValuesList.js b/src/components/LabelBrowser/ValuesList.js index cd5ac796..ee0aacbd 100644 --- a/src/components/LabelBrowser/ValuesList.js +++ b/src/components/LabelBrowser/ValuesList.js @@ -30,9 +30,9 @@ const ValuesListStyled = styled.div` background: ${(props) => props.theme.widgetContainer}; .valuelist-content { small { - color: ${(props) => props.theme.textColor} !important; + color: ${(props) => props.theme.textColor}; background: ${(props) => props.theme.buttonDefault} !important; - border: 1px solid ${(props) => props.theme.buttonBorder} !important; + border: 1px solid ${(props) => props.theme.buttonBorder}; margin: 5px; padding: 4px 8px; border-radius: 3px; @@ -104,20 +104,17 @@ export const ValuesList = (props) => { const dispatch = useDispatch(); const debug = useSelector((store) => store.debugMode); const apiUrl = useSelector((store) => store.apiUrl); - if (debug) console.log("🚧 LOGIC/LabelBrowser/ValuesList", apiUrl); + const labelsBrowserOpen = useSelector((store) => store.labelsBrowserOpen); const CLEAR = "clear"; - useEffect(() => { - dispatch(loadLabels(apiUrl)); - }, [apiUrl]); - useEffect(() => { setLabelList(labels); // LABELS }, [labels]); const handleRefresh = (e) => { e.preventDefault(); + console.log("event handled") dispatch(loadLabels(apiUrl)); }; diff --git a/src/components/LabelBrowser/components/ShowLabelsButton/ShowLabelsButton.js b/src/components/LabelBrowser/components/ShowLabelsButton/ShowLabelsButton.js index fb47c0fe..18d775dc 100644 --- a/src/components/LabelBrowser/components/ShowLabelsButton/ShowLabelsButton.js +++ b/src/components/LabelBrowser/components/ShowLabelsButton/ShowLabelsButton.js @@ -26,9 +26,9 @@ export default function ShowLabelsButton({ isMobile={isMobile} > {labelsBrowserOpen ? ( - + ) : ( - + )}{" "} {LOG_BROWSER} diff --git a/src/components/LabelBrowser/components/styled/index.js b/src/components/LabelBrowser/components/styled/index.js index 2436553e..31255b81 100644 --- a/src/components/LabelBrowser/components/styled/index.js +++ b/src/components/LabelBrowser/components/styled/index.js @@ -29,6 +29,7 @@ export const ShowLabelsBtn = styled(BtnSmall)` transition: 0.25s all; justify-content: flex-start; color: ${({ theme }) => theme.textColor}; + height: 28px; &:hover { background: ${({ theme }) => theme.buttonHover}; } @@ -42,7 +43,7 @@ export const ShowLabelsBtn = styled(BtnSmall)` export const QueryBarContainer = styled.div` display: flex; padding: 3px 6px; - margin: 5px 0px; + margin-top:5px; margin-left: 0px; background: ${({ theme }) => theme.widgetContainer}; flex-wrap: wrap; @@ -62,6 +63,7 @@ export const ShowLogsBtn = styled(BtnSmall)` background: ${(props) => props.theme.buttonDefault}; border:1px solid ${(props)=>props.theme.buttonBorder}; cursor: not-allowed; + color: ${props => props.theme.textColor}; } @media screen and (max-width: 864px) { display: ${(props) => (props.isMobile ? "flex" : "none")}; diff --git a/src/components/LabelBrowser/helpers/querybuilder.js b/src/components/LabelBrowser/helpers/querybuilder.js index 7de52e05..219e7e1f 100644 --- a/src/components/LabelBrowser/helpers/querybuilder.js +++ b/src/components/LabelBrowser/helpers/querybuilder.js @@ -1,11 +1,42 @@ import { setQuery } from "../../../actions"; import store from "../../../store/store"; -export function queryBuilder(labels) { +export const PIPE_PARSE = [ + { + label: "json", + }, + { + label: "regexp", + text: 'regexp ""', + }, + { + label: "logfmt", + }, + { + label: "pattern", + }, +]; + +const pipeParseOpts = ["json", "regexp", "logfmt", "pattern", "~", "="]; + +export function queryBuilder(labels, hasPipe = false, pipeLabels = []) { const actualQuery = store.getState().query; const preTags = actualQuery.split("{")[0]; - const postTags = actualQuery.split("}")[1]; + let postTags = ""; + + if (hasPipe) { + postTags = actualQuery.split("}")[1]; + const expParse = actualQuery.split(/[|]/); + if (pipeParseOpts.some((s) => expParse[1].includes(s))) { + const pipeTags = ` | ${pipeLabels}`; + postTags = postTags.toString().concat(pipeTags.toString()); + } + } else { + postTags = actualQuery.split("}")[1]; + } + const selectedLabels = []; + for (const label of labels) { if (label.selected && label.values && label.values.length > 0) { const selectedValues = label.values @@ -27,12 +58,14 @@ export function queryBuilder(labels) { }); } } + return [preTags, "{", selectedLabels.join(","), "}", postTags].join(""); } -export function queryBuilderWithLabels() { +export function queryBuilderWithLabels(hasPipe = false, pipeLabels = []) { const labels = store.getState().labels; - const query = queryBuilder(labels); + const query = queryBuilder(labels, hasPipe, pipeLabels); + store.dispatch(setQuery(query)); } diff --git a/src/components/QueryTypeBar/actions/setQueryResolution.js b/src/components/QueryTypeBar/actions/setQueryResolution.js new file mode 100644 index 00000000..8eea64f5 --- /dev/null +++ b/src/components/QueryTypeBar/actions/setQueryResolution.js @@ -0,0 +1,6 @@ +export const setQueryResolution = (queryResolution) => (dispatch) => { + dispatch({ + type: "SET_QUERY_RESOLUTION", + queryResolution, + }); +}; diff --git a/src/components/QueryTypeBar/components/QueryLimit.js b/src/components/QueryTypeBar/components/QueryLimit.js new file mode 100644 index 00000000..14d1aad7 --- /dev/null +++ b/src/components/QueryTypeBar/components/QueryLimit.js @@ -0,0 +1,53 @@ +import styled from "@emotion/styled"; +import { useEffect, useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { setQueryLimit } from "../../../actions"; + +const InputGroup = styled.div` + display: flex; + margin-right: 10px; +`; + +const Label = styled.div` + color: ${(props) => props.theme.textColor}; + background: ${(props) => props.theme.buttonInactive}; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + padding: 0px 8px; +`; + +const Input = styled.input` + flex: 1; + background: ${(props) => props.theme.inputBg}; + color: ${(props) => props.theme.textColor}; + border: 1px solid ${(props) => props.theme.buttonBorder}; + border-radius: 3px; + max-width: 60px; + padding-left: 8px; +`; + +export default function QueryLimit() { + const dispatch = useDispatch(); + const limit = useSelector((store) => store.limit); + const [editedValue, setEditedValue] = useState(limit); + + useEffect(() => { + setEditedValue(limit); + }, [limit, setEditedValue]); + + function onLimitChange(e) { + dispatch(setQueryLimit(e.target.value)); + } + return ( + + + + + ); +} diff --git a/src/components/QueryTypeBar/components/QueryResolution.js b/src/components/QueryTypeBar/components/QueryResolution.js new file mode 100644 index 00000000..cd55d805 --- /dev/null +++ b/src/components/QueryTypeBar/components/QueryResolution.js @@ -0,0 +1,63 @@ +import styled from "@emotion/styled"; +import { useState } from "react"; +import { useDispatch } from "react-redux"; +import { setQueryResolution } from "../actions/setQueryResolution"; +import { DEFAULT_RESOLUTION, RESOLUTION_OPTIONS } from "../helpers"; + +const Label = styled.div` + color: ${(props) => props.theme.textColor}; + background: ${(props) => props.theme.buttonInactive}; + + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + padding: 0px 8px; +`; + +const ResSelect = styled.select` + cursor: pointer; + + position: relative; + font-size: 14px; + color: ${(props) => props.theme.textColor}; + background: ${(props) => props.theme.inputBg}; + border: 1px solid ${(props) => props.theme.buttonBorder}; + border-radius: 3px; + padding: 4px 8px; + line-height: 20px; + flex: 1; + max-width: 70px; + &::-webkit-scrollbar { + width: 5px; + background: ${(props) => props.theme.inputBg}; + } + + &::-webkit-scrollbar-thumb { + border-radius: 10px; + background: ${(props) => props.theme.scrollbarThumb}; + } +`; + +export default function QueryResolution() { + const dispatch = useDispatch(); + const [resValue, setResValue] = useState(DEFAULT_RESOLUTION.value); + + function handleResChange(e) { + setResValue(parseInt(e.target.value)); + dispatch(setQueryResolution(parseInt(e.target.value))); + } + + return ( + <> + + + {RESOLUTION_OPTIONS.map((res, idx) => ( + + ))} + + + ); +} diff --git a/src/components/QueryTypeBar/components/QueryTypeSwitch.js b/src/components/QueryTypeBar/components/QueryTypeSwitch.js new file mode 100644 index 00000000..1abf5249 --- /dev/null +++ b/src/components/QueryTypeBar/components/QueryTypeSwitch.js @@ -0,0 +1,87 @@ +import styled from "@emotion/styled"; +import { useState } from "react"; +const Label = styled.div` +color:${props => props.theme.textColor}; +background: ${props => props.theme.buttonInactive}; +display:flex; +align-items: center; +justify-content: center; +font-size: 12px; +padding:0px 8px; +`; +const QuerySwitchCont = styled.div` + + display: flex; + align-items: center; + font-size: 12px; + background-color: ${(props) => props.theme.buttonInactive}; + + border: 1px solid ${(props) => props.theme.buttonBorder}; + color: ${(props) => props.theme.textColor}; + border-radius: 3px; + margin-right:10px; +`; + +const QuerySwitchBtn = styled.div` + cursor: pointer; + display: flex; + align-items: center; + background: ${(props) => + props.selected ? props.theme.buttonDefault : props.theme.buttonInactive}; + border-left: ${(props) => + props.position === "last" + ? `1px solid ${props.theme.buttonBorder}` + : "none" }; + border-right: ${(props) => + props.position === "first" + ? `1px solid ${props.theme.buttonBorder}` + : "none" }; + border-radius: ${({ position }) => + position === "first" + ? "3px 0px 0px 3px" + : position === "last" + ? "0px 3px 3px 0px" + : "0px"}; + flex: 1; + height: 90%; + + padding: 0px 12px; + font-size: 12px; + line-height: 20px; +`; +const getBtnPos = (key, arr) => { + const arrLen = arr.length; + return key === 0 ? "first" : key === arrLen - 1 ? "last" : "center"; +}; + +export default function QueryTypeSwitch(props) { + const { options, defaultActive, onChange } = props; + + const [activeBtn, setActiveBtn] = useState(defaultActive); + + function setButtonValue(value) { + setActiveBtn(value); + onChange(value) + } + + return (<> + + + + + + {options && + options.map((value, key, arr) => ( + setButtonValue(value.value)} + > + {value.label} + + ))} + + + ); +} diff --git a/src/components/QueryTypeBar/helpers/index.js b/src/components/QueryTypeBar/helpers/index.js new file mode 100644 index 00000000..f96808c9 --- /dev/null +++ b/src/components/QueryTypeBar/helpers/index.js @@ -0,0 +1,72 @@ +import { map } from "lodash"; + +function getTime(date) { + return Math.ceil(date.valueOf() * 1e6); +} + +function adjustInterval( + dynamicInterval, // number + resolution, // number + range // number time range +) { + let safeInterval = range / 1100; + if (safeInterval > 1) { + safeInterval = Math.ceil(safeInterval); + } + return Math.max(resolution * dynamicInterval, safeInterval); +} + +const MILLISECOND = 1; +const SECOND = 1000 * MILLISECOND; +const MINUTE = 60 * SECOND; +const HOUR = 60 * MINUTE; +const DAY = 24 * HOUR; + +function getIntervalInfo(timespanMs) { + let intervalMs = timespanMs; + + let interval = ""; + if (timespanMs < SECOND * 5) { + intervalMs = MILLISECOND; + interval = "1ms"; + } else if (intervalMs > HOUR) { + intervalMs = DAY; + interval = "1d"; + } else if (intervalMs > MINUTE) { + intervalMs = HOUR; + interval = "1h"; + } else if (intervalMs > SECOND) { + intervalMs = MINUTE; + interval = "1m"; + } else { + intervalMs = SECOND; + interval = "1s"; + } + console.log(intervalMs) + return { interval, intervalMs }; +} + +export const DEFAULT_RESOLUTION = { + value: 1, + label: "1/1", +}; + +export const RESOLUTION_OPTIONS = [DEFAULT_RESOLUTION].concat( + map([2, 3, 4, 5, 10], (value) => ({ + value, + label: "1/" + value, + })) +); + + +export default function adjustedStep(target, options) { + + const resolution = target.resolution || DEFAULT_RESOLUTION.value; + const startNs = getTime(options.range.from, false); + const endNs = getTime(options.range.to, true); + const rangeMs = Math.ceil((endNs - startNs) / 1e6); + const { intervalMs } = getIntervalInfo(rangeMs); + const intl = adjustInterval(intervalMs || 1000, resolution, rangeMs); + console.log(intl) + return Math.ceil(intl) / 1000; +} diff --git a/src/components/QueryTypeBar/index.js b/src/components/QueryTypeBar/index.js new file mode 100644 index 00000000..f283d7bd --- /dev/null +++ b/src/components/QueryTypeBar/index.js @@ -0,0 +1,49 @@ +import { ThemeProvider } from "@emotion/react"; +import styled from "@emotion/styled"; +import { useState, useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { setQueryType } from "../../actions"; +import { themes } from "../../theme/themes"; +import QueryLimit from "./components/QueryLimit"; +import QueryTypeSwitch from "./components/QueryTypeSwitch"; + +const QueryTypeCont = styled.div` + display: flex; + padding: 4px; + background: ${(props) => props.theme.widgetContainer}; + color: ${(props) => props.color}; + height: 26px; +`; + +export default function QueryTypeBar() { + const dispatch = useDispatch(); + const theme = useSelector((store) => store.theme); + const queryType = useSelector((store) => store.queryType); + const [queryTypeSwitch, setQueryTypeSwitch] = useState(queryType); + + useEffect(() => { + setQueryTypeSwitch(queryType); + }, [queryType, setQueryTypeSwitch]); + + const SWITCH_OPTIONS = [ + { value: "range", label: "Range" }, + { value: "instant", label: "Instant" }, + ]; + + function onSwitchChange(e) { + dispatch(setQueryType(e)); + } + + return ( + + + + + + + ); +} diff --git a/src/components/StatusBar/components/apiselector/ApiSelector.js b/src/components/StatusBar/components/apiselector/ApiSelector.js index 0215a753..4e1322d9 100644 --- a/src/components/StatusBar/components/apiselector/ApiSelector.js +++ b/src/components/StatusBar/components/apiselector/ApiSelector.js @@ -28,7 +28,7 @@ export function ApiSelector() { useEffect(() => { setEditedUrl(apiUrl); - + console.log("labels loaded from apiURL") dispatch(loadLabels(apiUrl)); }, [apiUrl]); @@ -57,7 +57,7 @@ export function ApiSelector() { dispatch(setApiUrl(editedUrl)); setEditedUrl(editedUrl); - + console.log("set from onurlsubmit") dispatch(loadLabels(editedUrl)); dispatch(setLabelsBrowserOpen(false)); diff --git a/src/components/Switch/Switch.js b/src/components/Switch/Switch.js new file mode 100644 index 00000000..6901c9b6 --- /dev/null +++ b/src/components/Switch/Switch.js @@ -0,0 +1,87 @@ +import styled from "@emotion/styled"; +import { useState } from "react"; +const Label = styled.div` + color: ${(props) => props.theme.textColor}; + background: ${(props) => props.theme.buttonInactive}; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + padding: 0px 8px; +`; +const SwitchCont = styled.div` + + display: flex; + align-items: center; + font-size: 12px; + background-color: ${(props) => props.theme.buttonInactive}; + + border: 1px solid ${(props) => props.theme.buttonBorder}; + color: ${(props) => props.theme.textColor}; + border-radius: 3px; + margin-right: 10px; +`; + +const SwitchBtn = styled.div` + cursor: pointer; + display: flex; + align-items: center; + background: ${(props) => + props.selected + ? props.theme.buttonDefault + : props.theme.buttonInactive}; + border-left: ${(props) => + props.position === "last" + ? `1px solid ${props.theme.buttonBorder}` + : "none"}; + border-right: ${(props) => + props.position === "first" + ? `1px solid ${props.theme.buttonBorder}` + : "none"}; + border-radius: ${({ position }) => + position === "first" + ? "3px 0px 0px 3px" + : position === "last" + ? "0px 3px 3px 0px" + : "0px"}; + flex: 1; + height: 90%; + + padding: 0px 12px; + font-size: 12px; + line-height: 20px; +`; +const getBtnPos = (key, arr) => { + const arrLen = arr.length; + return key === 0 ? "first" : key === arrLen - 1 ? "last" : "center"; +}; + +export default function QueryTypeSwitch(props) { + const { options, defaultActive, onChange, label } = props; + + const [activeBtn, setActiveBtn] = useState(defaultActive); + + function setButtonValue(value) { + setActiveBtn(value); + onChange(value); + } + + return ( + <> + + + {options && + options.map((value, key, arr) => ( + setButtonValue(value.value)} + > + {value.label} + + ))} + + + ); +} diff --git a/src/helpers/UpdateStateFromQueryParams.js b/src/helpers/UpdateStateFromQueryParams.js index d4e7a731..7e7ce589 100644 --- a/src/helpers/UpdateStateFromQueryParams.js +++ b/src/helpers/UpdateStateFromQueryParams.js @@ -2,20 +2,22 @@ import * as moment from "moment"; import { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useLocation } from "react-router-dom"; + import { setApiUrl, setIsSubmit, setLabels, setQuery, setQueryLimit, + setQueryTime, setQueryStep, + setQueryType, setStartTime, setStopTime, setTheme, - } from "../actions"; -import loadLabels from "../actions/loadLabels" +import loadLabels from "../actions/loadLabels"; import loadLabelValues from "../actions/loadLabelValues"; import setFromTime from "../actions/setFromTime"; import setIsEmbed from "../actions/setIsEmbed"; @@ -26,8 +28,8 @@ import { setUrlQueryParams } from "../actions/setUrlQueryParams"; import { environment } from "../environment/env.dev"; import store from "../store/store"; export function UpdateStateFromQueryParams() { - - const isLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches; + const isLightTheme = window.matchMedia("(prefers-color-scheme: light)") + .matches; const { hash } = useLocation(); const dispatch = useDispatch(); @@ -36,48 +38,63 @@ export function UpdateStateFromQueryParams() { const stop = useSelector((store) => store.stop); const limit = useSelector((store) => store.limit); const from = useSelector((store) => store.from); - const to = useSelector((store)=> store.to); + const to = useSelector((store) => store.to); const step = useSelector((store) => store.step); - const labels = useSelector((store) => store.labels) + const labels = useSelector((store) => store.labels); const apiUrl = useSelector((store) => store.apiUrl); const isSubmit = useSelector((store) => store.isSubmit); const isEmbed = useSelector((store) => store.isEmbed); const query = useSelector((store) => store.query); - const theme = useSelector((store) => isLightTheme ? 'light' : store.theme) + const queryType = useSelector((store) => store.queryType); + const time = useSelector((store) => store.time); + const theme = useSelector((store) => + isLightTheme ? "light" : store.theme + ); const STORE_KEYS = { apiUrl, query, + queryType, start, limit, step, end: stop, from, to, + time, isSubmit, isEmbed, - theme + theme, }; const STORE_ACTIONS = { apiUrl: setApiUrl, query: setQuery, + queryType: setQueryType, start: setStartTime, limit: setQueryLimit, step: setQueryStep, end: setStopTime, from: setFromTime, to: setToTime, + time: setQueryTime, isSubmit: setIsSubmit, isEmbed: setIsEmbed, - theme: setTheme + theme: setTheme, }; - const STRING_VALUES = ["limit", "step", "apiUrl", "theme"]; + const STRING_VALUES = [ + "limit", + "step", + "apiUrl", + "theme", + "queryType", + "time", + ]; const QUERY_VALUE = "query"; - const TIME_VALUES = ["start", "end",]; + const TIME_VALUES = ["start", "end"]; const BOOLEAN_VALUES = ["isSubmit", "isEmbed"]; @@ -106,10 +123,6 @@ export function UpdateStateFromQueryParams() { startParams[param] !== "" ) { dispatch(STORE_ACTIONS[param](startParams[param])); - - if(param === 'apiUrl') { - dispatch(loadLabels(startParams[param])) - } } else if ( QUERY_VALUE === param && startParams[param] !== "" @@ -117,6 +130,7 @@ export function UpdateStateFromQueryParams() { const parsedQuery = decodeURIComponent( startParams[param] ); + dispatch(STORE_ACTIONS[param](parsedQuery)); } else if ( TIME_VALUES.includes(param) && @@ -139,12 +153,14 @@ export function UpdateStateFromQueryParams() { } }); - decodeQuery(decodeURIComponent(startParams.query),apiUrl,labels); + decodeQuery( + decodeURIComponent(startParams.query), + apiUrl, + labels + ); dispatch(setLabelsBrowserOpen(false)); - } } else { - dispatch(setApiUrl(environment.apiUrl)); const allParams = STRING_VALUES.concat(TIME_VALUES); allParams.push(QUERY_VALUE); @@ -174,7 +190,6 @@ export function UpdateStateFromQueryParams() { window.location.hash = urlFromHash; } - }, []); useEffect(() => { @@ -219,8 +234,7 @@ export function UpdateStateFromQueryParams() { }, [STORE_KEYS]); } -export function decodeQuery(query, apiUrl, labels=[]) { - +export function decodeQuery(query, apiUrl, labels = []) { const queryArr = query ?.match(/[^{\}]+(?=})/g, "$1") ?.map((m) => m.split(",")) @@ -270,11 +284,10 @@ export function decodeQuery(query, apiUrl, labels=[]) { }; labelObj.values.push(valueObj); labelsFromQuery.push(labelObj); - } }); - const newLabels = labels + const newLabels = labels; newLabels?.forEach((label) => { if (label.selected && label.values > 0) { @@ -298,10 +311,12 @@ export function decodeQuery(query, apiUrl, labels=[]) { await store.dispatch( loadLabelValues(cleanLabel, newLabels, apiUrl) ); + const labelsWithValues = store.getState().labels; const labelWithValues = labelsWithValues.find( (item) => item?.name === label?.name ); + let values = labelWithValues.values; values = label.values.concat(values); values = values diff --git a/src/plugins/charts/index.js b/src/plugins/charts/index.js index 44bcf4bb..789aafda 100644 --- a/src/plugins/charts/index.js +++ b/src/plugins/charts/index.js @@ -1,11 +1,8 @@ import "./jquery-loader"; import ReactFlot from "react-flot"; import "react-flot/flot/jquery.flot.time.min"; - import "react-flot/flot/jquery.flot.selection.min"; - import "react-flot/flot/jquery.flot.crosshair.min"; - import loadLogs from "../../actions/loadLogs"; import { useDispatch } from "react-redux"; import { setStartTime, setStopTime, setTimeRangeLabel } from "../../actions"; @@ -60,11 +57,6 @@ export default function ClokiChart({ matrixData }) { } } - /** - * - * Set chart types - */ - function plotChartData(data, type, element) { const chartSeries = setChartTypeSeries(type); const { timeformat, min, max } = formatDateRange(data); @@ -73,7 +65,7 @@ export default function ClokiChart({ matrixData }) { data, $q.extend(true, {}, chartOptions, { ...chartSeries, - xaxis: { timeformat, min, max, }, + xaxis: { timeformat, min, max }, }) ); } @@ -139,10 +131,10 @@ export default function ClokiChart({ matrixData }) { min: ranges.xaxis.from - 100000, max: ranges.xaxis.to + 100000, timeformat: formatDateRange(newData).timerange, - }, }) ); + $q(chartRef.current).UseTooltip(plot); setTimeout(() => { const fromTime = ranges.xaxis.from; @@ -160,10 +152,12 @@ export default function ClokiChart({ matrixData }) { const toLabel = format(toTs, "yyyy/MM/dd HH:mm:ss"); const timeRangeLabel = `${fromLabel}-${toLabel}`; + dispatch(setStopTime(toTs)); dispatch(setStartTime(fromTs)); dispatch(setTimeRangeLabel(timeRangeLabel)); + dispatch(loadLogs()); }, 400); } catch (e) { @@ -171,11 +165,6 @@ export default function ClokiChart({ matrixData }) { } } - /** - * - *Isolate Series clicking label - */ - function onLabelClick(e, v) { let newList = []; const lSelected = @@ -218,7 +207,7 @@ export default function ClokiChart({ matrixData }) { $q.extend(true, {}, chartOptions, { series: getSeriesFromChartType(chartType), - xaxis: { timeformat, min, max, }, + xaxis: { timeformat, min, max }, }) ); @@ -244,7 +233,7 @@ export default function ClokiChart({ matrixData }) { newData, $q.extend(true, {}, chartOptions, { series: getSeriesFromChartType(chartType), - xaxis: { timeformat, min, max, }, + xaxis: { timeformat, min, max }, }) ); @@ -279,7 +268,7 @@ export default function ClokiChart({ matrixData }) { newData, $q.extend(true, {}, chartOptions, { series: getSeriesFromChartType(chartType), - xaxis: { timeformat, min, max, }, + xaxis: { timeformat, min, max }, }) ); @@ -300,7 +289,7 @@ export default function ClokiChart({ matrixData }) { }; return ( -
+
diff --git a/src/plugins/queryhistory/index.js b/src/plugins/queryhistory/index.js index 7605b3f2..09161321 100644 --- a/src/plugins/queryhistory/index.js +++ b/src/plugins/queryhistory/index.js @@ -766,6 +766,7 @@ const QueryHistory = (props) => { style={{ maxHeight: "250px" }} open={historyOpen} variant={"persistent"} + theme={themes[theme]} > Embed View diff --git a/src/store/createInitialState.js b/src/store/createInitialState.js index 46f5c51f..50783d80 100644 --- a/src/store/createInitialState.js +++ b/src/store/createInitialState.js @@ -25,9 +25,11 @@ export default function initialState() { linksHistory: linkService.getAll() || [], timeRange: [], query: urlState.query || "", + queryType: urlState.queryType || 'range', logs: [], matrixData: [], loading: false, + queryResolution: 1, start: urlState.start || new Date( @@ -35,6 +37,7 @@ export default function initialState() { .subtract(5, "minutes") .format("YYYY-MM-DDTHH:mm:ss.SSSZ") ), + time: urlState.time || '', stop: urlState.end || new Date(moment(Date.now()).format("YYYY-MM-DDTHH:mm:ss.SSSZ")), @@ -60,6 +63,7 @@ export default function initialState() { chartType: "line", notifications: [], theme: urlState.theme || "dark", + isEmptyView: false, }; const debug = state.debugMode; if (debug) console.log("🚧 LOGIC/ INITIAL STATE ::: ", state); diff --git a/src/store/reducer.js b/src/store/reducer.js index eba0a35c..50c922cc 100644 --- a/src/store/reducer.js +++ b/src/store/reducer.js @@ -40,6 +40,8 @@ const reducer = (state, action) => { return { ...state, apiErrors: action.apiErrors }; case "SET_URL_QUERY_PARAMS": return { ...state, urlQueryParams: action.urlQueryParams }; + case "SET_QUERY_TYPE": + return { ...state, queryType: action.queryType }; case "SET_URL_LOCATION": return { ...state, urlLocation: action.urlLocation }; case "SET_IS_SUBMIT": @@ -62,8 +64,14 @@ const reducer = (state, action) => { return { ...state, notifications: action.payload }; case "SET_DEBUG_MODE": return { ...state, debugMode: action.debugMode }; - case "SET_THEME": - return {...state, theme: action.theme}; + case "SET_THEME": + return { ...state, theme: action.theme }; + case "SET_QUERY_TIME": + return { ...state, time: action.time }; + case "SET_QUERY_RESOLUTION": + return {...state, queryResolution: action.queryResolution}; + case "SET_IS_EMPTY_VIEW": + return {...state, isEmptyView: action.isEmptyView}; default: return { ...state }; } diff --git a/src/theme/light.js b/src/theme/light.js index aeb08a9d..a839b939 100644 --- a/src/theme/light.js +++ b/src/theme/light.js @@ -74,7 +74,7 @@ const widgetTitle = lightgrey.lg300; const widgetTitleBorder = lightgrey.lg700; const buttonDefault = white.w100; const buttonHover = white.w700; -const buttonInactive = black.b400; +const buttonInactive = lightgrey.lg10; const buttonBorder = lightgrey.lg100; const buttonText = white.w200; const inputTextFocus = orange.or100;