diff --git a/web/src/components/DateTime.vue b/web/src/components/DateTime.vue index 80cd059b6a..04d740ab24 100644 --- a/web/src/components/DateTime.vue +++ b/web/src/components/DateTime.vue @@ -36,6 +36,7 @@ along with this program. If not, see . self="top left" no-route-dismiss @before-show="onBeforeShow" + @before-hide="onBeforeHide" >
{ - router.currentRoute.value.query?.from; - }, - () => { - if ( - router.currentRoute.value.query.hasOwnProperty("from") && - router.currentRoute.value.query.hasOwnProperty("to") - ) { - selectedType.value = "absolute"; - selectedTime.value.startTime = timestampToTimezoneDate( - router.currentRoute.value.query?.from / 1000, - store.state.timezone, - "HH:mm", - ); - selectedTime.value.endTime = timestampToTimezoneDate( - router.currentRoute.value.query?.to / 1000, - store.state.timezone, - "HH:mm", - ); - selectedDate.value.from = timestampToTimezoneDate( - router.currentRoute.value.query?.from / 1000, - store.state.timezone, - "yyyy/MM/dd", - ); - selectedDate.value.to = timestampToTimezoneDate( - router.currentRoute.value.query?.to / 1000, - store.state.timezone, - "yyyy/MM/dd", - ); - saveDate("absolute"); - } - }, - { deep: true }, - ); - - // watch( - // () => props.defaultAbsoluteTime, - // (value) => { - // console.log("defaultAbsoluteTime", value); - // if ( - // (value.startTime !== datePayload.value.startTime || - // value.endTime !== datePayload.value.endTime) && - // store.state.savedViewFlag == false - // ) { - // selectedType.value = props.defaultType; - // setAbsoluteTime(value.startTime, value.endTime); - // } - // }, - // { - // deep: true, - // } - // ); - const setRelativeDate = (period, value) => { selectedType.value = "relative"; relativePeriod.value = period; @@ -629,19 +573,28 @@ export default defineComponent({ if (periodValue) { relativeValue.value = parseInt(periodValue); } - - selectedType.value = "relative"; } }; const resetTime = (startTime, endTime) => { if (!startTime || !endTime) { var dateString = new Date().toLocaleDateString("en-ZA"); - selectedDate.value.from = dateString; - selectedDate.value.to = dateString; + + if (!selectedDate.value.from) selectedDate.value.from = dateString; + + if (!selectedDate.value.to) selectedDate.value.to = dateString; + if (!startTime) selectedTime.value.startTime = "00:00"; - if (!endTime) selectedTime.value.endTime = "23:59"; + if (!endTime) { + const endDateTime = convertUnixTime(new Date().getTime() * 1000); + + // If the selected date is today, set the end time to the current time + if (selectedDate.value.to === endDateTime.date) { + selectedTime.value.endTime = endDateTime.time; + } else selectedTime.value.endTime = "23:59"; + } + return; } return; @@ -731,6 +684,11 @@ export default defineComponent({ // if (props.modelValue) selectedDate.value = cloneDeep(props.modelValue); }; + const onBeforeHide = () => { + if (selectedType.value === "absolute") + resetTime(selectedTime.value.startTime, selectedTime.value.endTime); + }; + const getPeriodLabel = computed(() => { const periodMapping = { m: "Minutes", @@ -828,6 +786,7 @@ export default defineComponent({ if (dateobj.type === "relative") { setRelativeTime(dateobj.relativeTimePeriod); + selectedType.value = "relative"; } else { if ( dateobj.hasOwnProperty("selectedDate") && @@ -1030,6 +989,7 @@ export default defineComponent({ relativePeriodsMaxValue, relativePeriodsSelect, computeRelativePeriod, + onBeforeHide, }; }, computed: { diff --git a/web/src/components/QueryEditor.vue b/web/src/components/QueryEditor.vue index ffd06da6dc..c1b64fc4df 100644 --- a/web/src/components/QueryEditor.vue +++ b/web/src/components/QueryEditor.vue @@ -209,12 +209,12 @@ export default defineComponent({ }); window.addEventListener("click", () => { - editorObj.layout(); + editorObj?.layout(); }); window.addEventListener("resize", async () => { await nextTick(); - editorObj.layout(); + editorObj?.layout(); // queryEditorRef.value.resetEditorLayout(); }); }; @@ -307,7 +307,7 @@ export default defineComponent({ const setValue = (value: string) => { if (editorObj?.setValue) { editorObj.setValue(value); - editorObj.layout(); + editorObj?.layout(); } }; @@ -361,7 +361,7 @@ export default defineComponent({ }; const resetEditorLayout = () => { - editorObj.layout(); + editorObj?.layout(); }; const triggerAutoComplete = async (value: string) => { diff --git a/web/src/components/alerts/AlertList.vue b/web/src/components/alerts/AlertList.vue index 1b8582f81d..e05ec3bb9b 100644 --- a/web/src/components/alerts/AlertList.vue +++ b/web/src/components/alerts/AlertList.vue @@ -21,8 +21,9 @@ along with this program. If not, see . data-test="alert-list-page" class="q-pa-none flex" style="height: calc(100vh - 57px)" + :class="store.state.theme === 'dark' ? 'dark-theme' : 'light-theme'" > -
+
. @click="showAddUpdateFn(props)" > + icon="content_copy" + :title="t('alerts.clone')" + class="q-ml-xs" + padding="sm" + unelevated + size="sm" + round + flat + @click.stop="duplicateAlert(props.row)" + data-test="alert-clone" + > .
{{ props.row.sql }}
+
@@ -346,7 +345,7 @@ export default defineComponent({ components: { QTablePagination, AddAlert: defineAsyncComponent( - () => import("@/components/alerts/AddAlert.vue") + () => import("@/components/alerts/AddAlert.vue"), ), NoData, ConfirmDialog, @@ -371,21 +370,21 @@ export default defineComponent({ const isUpdated: any = ref(false); const confirmDelete = ref(false); const splitterModel = ref(220); - const showForm = ref(false); + const showForm = ref(false); const indexOptions = ref([]); const schemaList = ref([]); const streams: any = ref({}); const isFetchingStreams = ref(false); - const isSubmitting = ref(false); + const isSubmitting = ref(false); const { getStreams } = useStreams(); - const toBeCloneAlertName = ref (""); - const toBeCloneUUID = ref(""); - const toBeClonestreamType = ref(''); - const toBeClonestreamName = ref(''); - const streamTypes = ref(['logs', 'metrics', 'traces']); - const streamNames : Ref = ref([]); + const toBeCloneAlertName = ref(""); + const toBeCloneUUID = ref(""); + const toBeClonestreamType = ref(""); + const toBeClonestreamName = ref(""); + const streamTypes = ref(["logs", "metrics", "traces"]); + const streamNames: Ref = ref([]); const alertStateLoadingMap: Ref<{ [key: string]: boolean }> = ref({}); const folders = ref([ { @@ -409,27 +408,6 @@ export default defineComponent({ align: "left", sortable: true, }, - { - name: "alert_type", - field: "alert_type", - label: t("alerts.alertType"), - align: "left", - sortable: true, - }, - { - name: "stream_type", - field: "stream_type", - label: t("alerts.streamType"), - align: "left", - sortable: true, - }, - { - name: "stream_name", - field: "stream_name", - label: t("alerts.stream_name"), - align: "left", - sortable: true, - }, { name: "owner", field: "owner", @@ -488,7 +466,7 @@ export default defineComponent({ "name", false, "", - store.state.selectedOrganization.identifier + store.state.selectedOrganization.identifier, ) .then((res) => { var counter = 1; @@ -526,8 +504,12 @@ export default defineComponent({ description: data.description, uuid: data.uuid, owner: data.owner, - last_triggered_at:convertUnixToQuasarFormat(data.last_triggered_at), - last_satisfied_at:convertUnixToQuasarFormat(data.last_satisfied_at), + last_triggered_at: convertUnixToQuasarFormat( + data.last_triggered_at, + ), + last_satisfied_at: convertUnixToQuasarFormat( + data.last_satisfied_at, + ), }; }); alertsRows.value.forEach((alert: AlertListItem) => { @@ -569,7 +551,7 @@ export default defineComponent({ () => router.currentRoute.value.query.action, (action) => { if (!action) showAddAlertDialog.value = false; - } + }, ); const getDestinations = () => { destinationService @@ -584,7 +566,7 @@ export default defineComponent({ type: "negative", message: "Error while fetching destinations.", timeout: 3000, - }) + }), ); }; @@ -601,7 +583,7 @@ export default defineComponent({ type: "negative", message: "Error while fetching templates.", timeout: 3000, - }) + }), ); }; const perPageOptions: any = [ @@ -626,35 +608,37 @@ export default defineComponent({ const changeMaxRecordToReturn = (val: any) => { maxRecordToReturn.value = val; }; - - function convertUnixToQuasarFormat(unixMicroseconds : any) { - if(!unixMicroseconds) return ""; - const unixSeconds = unixMicroseconds / 1e6; - const dateToFormat = new Date(unixSeconds * 1000); - const formattedDate = dateToFormat.toISOString(); - return date.formatDate(formattedDate, "YYYY-MM-DDTHH:mm:ssZ"); -} + + function convertUnixToQuasarFormat(unixMicroseconds: any) { + if (!unixMicroseconds) return ""; + const unixSeconds = unixMicroseconds / 1e6; + const dateToFormat = new Date(unixSeconds * 1000); + const formattedDate = dateToFormat.toISOString(); + return date.formatDate(formattedDate, "YYYY-MM-DDTHH:mm:ssZ"); + } const addAlert = () => { showAddAlertDialog.value = true; }; - const duplicateAlert = (row : any) =>{ - toBeCloneUUID.value = row.uuid + const duplicateAlert = (row: any) => { + toBeCloneUUID.value = row.uuid; toBeCloneAlertName.value = row.name; toBeClonestreamName.value = ""; toBeClonestreamType.value = ""; - + showForm.value = true; - } - const submitForm = async () =>{ - const alertToBeCloned = alerts.value.find((alert) => alert.uuid === toBeCloneUUID.value) as Alert; + }; + const submitForm = async () => { + const alertToBeCloned = alerts.value.find( + (alert) => alert.uuid === toBeCloneUUID.value, + ) as Alert; const dismiss = $q.notify({ - spinner: true, - message: "Please wait...", - timeout: 2000, - }); + spinner: true, + message: "Please wait...", + timeout: 2000, + }); if (!alertToBeCloned) { $q.notify({ @@ -664,7 +648,7 @@ export default defineComponent({ }); return; } - if(!toBeClonestreamType.value){ + if (!toBeClonestreamType.value) { $q.notify({ type: "negative", message: "Please select stream type ", @@ -672,7 +656,7 @@ export default defineComponent({ }); return; } - if(!toBeClonestreamName.value){ + if (!toBeClonestreamName.value) { $q.notify({ type: "negative", message: "Please select stream name", @@ -682,22 +666,24 @@ export default defineComponent({ } isSubmitting.value = true; - alertToBeCloned.name = toBeCloneAlertName.value; - alertToBeCloned.stream_name = toBeClonestreamName.value; - alertToBeCloned.stream_type = toBeClonestreamType.value; - - try{ - alertsService.create( + alertToBeCloned.name = toBeCloneAlertName.value; + alertToBeCloned.stream_name = toBeClonestreamName.value; + alertToBeCloned.stream_type = toBeClonestreamType.value; + + try { + alertsService + .create( store.state.selectedOrganization.identifier, alertToBeCloned.stream_name, alertToBeCloned.stream_type, alertToBeCloned, - ).then((res) => { + ) + .then((res) => { dismiss(); if (res.data.code == 200) { $q.notify({ type: "positive", - message: "Alert Cloned Successfully", + message: "Alert Cloned Successfully", timeout: 2000, }); showForm.value = false; @@ -716,27 +702,24 @@ export default defineComponent({ type: "negative", message: e.response.data.message, timeout: 2000, - }); - }).finally(() => { - isSubmitting.value = false; + }); }) - - } - catch(e : any) { + .finally(() => { + isSubmitting.value = false; + }); + } catch (e: any) { showForm.value = true; isSubmitting.value = false; - $q.notify({ - type: "negative", - message: e.data.message, - timeout: 2000, - }); + $q.notify({ + type: "negative", + message: e.data.message, + timeout: 2000, + }); } - - - } + }; const showAddUpdateFn = (props: any) => { formData.value = alerts.value.find( - (alert: any) => alert.uuid === props.row?.uuid + (alert: any) => alert.uuid === props.row?.uuid, ) as Alert; let action; if (!props.row) { @@ -790,7 +773,7 @@ export default defineComponent({ store.state.selectedOrganization.identifier, selectedDelete.value.stream_name, selectedDelete.value.name, - selectedDelete.value.stream_type + selectedDelete.value.stream_type, ) .then((res: any) => { if (res.data.code == 200) { @@ -845,9 +828,9 @@ export default defineComponent({ }); return filteredOptions; }; - const updateStreamName = (selectedOption : any)=> { - toBeClonestreamName.value = selectedOption; - } + const updateStreamName = (selectedOption: any) => { + toBeClonestreamName.value = selectedOption; + }; const updateStreams = (resetStream = true) => { if (resetStream) toBeClonestreamName.value = ""; if (streams.value[toBeClonestreamType.value]) { @@ -865,7 +848,7 @@ export default defineComponent({ if (!toBeClonestreamType) return Promise.resolve(); isFetchingStreams.value = true; - return getStreams (toBeClonestreamType.value, false) + return getStreams(toBeClonestreamType.value, false) .then((res: any) => { streams.value[toBeClonestreamType.value] = res.list; schemaList.value = res.list; @@ -879,14 +862,13 @@ export default defineComponent({ .finally(() => (isFetchingStreams.value = false)); }; const filterStreams = (val: string, update: any) => { - streamNames.value = filterColumns(indexOptions.value, val, update) - + streamNames.value = filterColumns(indexOptions.value, val, update); }; const toggleAlertState = (row: any) => { alertStateLoadingMap.value[row.uuid] = true; const alert: Alert = alerts.value.find( - (alert) => alert.uuid === row.uuid + (alert) => alert.uuid === row.uuid, ) as Alert; alertsService .toggleState( @@ -894,7 +876,7 @@ export default defineComponent({ alert.stream_name, alert.name, !alert?.enabled, - alert.stream_type + alert.stream_type, ) .then(() => { alert.enabled = !alert.enabled; @@ -999,7 +981,31 @@ export default defineComponent({ } } +.alert-list-table { + th:last-child, + td:last-child { + position: sticky; + right: 0; + z-index: 1; + background: #ffffff; + box-shadow: -4px 0px 4px 0 rgba(0, 0, 0, 0.1); + } +} +.dark-theme { + th:last-child, + td:last-child { + background: var(--q-dark); + box-shadow: -4px 0px 4px 0 rgba(144, 144, 144, 0.1); + } +} + +.light-theme { + th:last-child, + td:last-child { + background: #ffffff; + } +} .alerts-tabs { .q-tabs { &--vertical { @@ -1027,7 +1033,7 @@ export default defineComponent({ } } } -.clone-alert-popup{ +.clone-alert-popup { width: 400px; } diff --git a/web/src/composables/useLogs.ts b/web/src/composables/useLogs.ts index 3970ede86e..0676f6dbe9 100644 --- a/web/src/composables/useLogs.ts +++ b/web/src/composables/useLogs.ts @@ -707,6 +707,11 @@ const useLogs = () => { ) : cloneDeep(searchObj.data.datetime); + if (searchObj.data.datetime.type === "relative") { + searchObj.data.datetime.startTime = timestamps.startTime; + searchObj.data.datetime.endTime = timestamps.endTime; + } + if ( timestamps.startTime != "Invalid Date" && timestamps.endTime != "Invalid Date" diff --git a/web/src/plugins/logs/Index.vue b/web/src/plugins/logs/Index.vue index 32410f0a63..c4d22ee622 100644 --- a/web/src/plugins/logs/Index.vue +++ b/web/src/plugins/logs/Index.vue @@ -287,13 +287,12 @@ import { getConsumableRelativeTime } from "@/utils/date"; import { cloneDeep } from "lodash-es"; import { buildSqlQuery, getFieldsFromQuery } from "@/utils/query/sqlUtils"; import useNotifications from "@/composables/useNotifications"; +import SearchBar from "@/plugins/logs/SearchBar.vue"; export default defineComponent({ name: "PageSearch", components: { - SearchBar: defineAsyncComponent( - () => import("@/plugins/logs/SearchBar.vue"), - ), + SearchBar, IndexList: defineAsyncComponent( () => import("@/plugins/logs/IndexList.vue"), ), diff --git a/web/src/plugins/logs/SearchBar.vue b/web/src/plugins/logs/SearchBar.vue index 51d48e948f..360d05e3f8 100644 --- a/web/src/plugins/logs/SearchBar.vue +++ b/web/src/plugins/logs/SearchBar.vue @@ -920,6 +920,7 @@ import ConfirmDialog from "@/components/ConfirmDialog.vue"; import { cloneDeep } from "lodash-es"; import useDashboardPanelData from "@/composables/useDashboardPanel"; import { inject } from "vue"; +import QueryEditor from "@/components/QueryEditor.vue"; const defaultValue: any = () => { return { @@ -934,9 +935,7 @@ export default defineComponent({ name: "ComponentSearchSearchBar", components: { DateTime, - QueryEditor: defineAsyncComponent( - () => import("@/components/QueryEditor.vue"), - ), + QueryEditor, SyntaxGuide, AutoRefreshInterval, ConfirmDialog, @@ -1155,10 +1154,10 @@ export default defineComponent({ const updateAutoComplete = (value) => { autoCompleteData.value.query = value; autoCompleteData.value.cursorIndex = - queryEditorRef.value.getCursorIndex(); + queryEditorRef?.value?.getCursorIndex(); autoCompleteData.value.fieldValues = props.fieldValues; autoCompleteData.value.popup.open = - queryEditorRef.value.triggerAutoComplete; + queryEditorRef?.value?.triggerAutoComplete; getSuggestions(); }; @@ -2350,7 +2349,7 @@ export default defineComponent({ searchObj.data.query = ""; } searchObj.data.editorValue = ""; - queryEditorRef.value.setValue(searchObj.data.query); + queryEditorRef.value?.setValue(searchObj.data.query); if (store.state.zoConfig.query_on_stream_selection == false) { handleRunQueryFn(); } diff --git a/web/src/plugins/logs/TenstackTable.vue b/web/src/plugins/logs/TenstackTable.vue index 34513e6034..00648d2800 100644 --- a/web/src/plugins/logs/TenstackTable.vue +++ b/web/src/plugins/logs/TenstackTable.vue @@ -162,46 +162,54 @@ along with this program. If not, see .
- - - -
- +
- - {{ t("search.functionErrorLabel") }} -
- - - - -
{{ functionErrorMsg }}
- - + + + {{ t("search.functionErrorLabel") }} +
+ + + + +
{{ functionErrorMsg }}
+ + + ([]); const tableRows = ref(props.rows); +const isFunctionErrorOpen = ref(false); + watch( () => props.columns, async (newVal) => { @@ -652,6 +663,7 @@ const expandRow = async (index: number) => { isCollapseOperation = true; } else { expandedRowIndices.value.push(index); + tableRows.value.splice(index + 1, 0, { isExpandedRow: true, ...(props.rows[index] as {}), @@ -694,6 +706,10 @@ const handleDataRowClick = (row: any, index: number) => { } }; +const expandFunctionError = () => { + isFunctionErrorOpen.value = !isFunctionErrorOpen.value; +}; + defineExpose({ parentRef, }); diff --git a/web/vite.config.ts b/web/vite.config.ts index 787687baf6..854f939e61 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -46,7 +46,7 @@ const enterpriseResolverPlugin = { const enterprisePath = path.resolve( __dirname, - `./src/enterprise/${fileName}` + `./src/enterprise/${fileName}`, ); const defaultPath = path.resolve(__dirname, `./src/${fileName}`); @@ -119,7 +119,7 @@ export default defineConfig({ alias: { "@": fileURLToPath(new URL("./src", import.meta.url)), "@enterprise": fileURLToPath( - new URL("./src/enterprise", import.meta.url) + new URL("./src/enterprise", import.meta.url), ), stream: "rollup-plugin-node-polyfills/polyfills/stream", events: "rollup-plugin-node-polyfills/polyfills/events", @@ -142,20 +142,21 @@ export default defineConfig({ brotliSize: true, }), ], - manualChunks: { - "o2cs-analytics": ["rudder-sdk-js"], - "o2cs-oo-rum": [ - "@openobserve/browser-logs", - "@openobserve/browser-rum", - ], - "o2cs-date-fns": ["date-fns", "date-fns-tz"], - }, output: { + manualChunks: { + "o2cs-analytics": ["rudder-sdk-js"], + "o2cs-oo-rum": [ + "@openobserve/browser-logs", + "@openobserve/browser-rum", + ], + "o2cs-date-fns": ["date-fns", "date-fns-tz"], + "editor.api": ["monaco-editor"], + }, chunkFileNames: ({ name }) => { if (name.startsWith("o2cs-")) { return `assets/vendor/${name}.[hash].js`; } - + if (name.includes("editor.api")) { return `assets/${name}.v1.js`; }