diff --git a/components/analysis/AnalysesTable.vue b/components/analysis/AnalysesTable.vue index e40cc16f..5a089231 100644 --- a/components/analysis/AnalysesTable.vue +++ b/components/analysis/AnalysesTable.vue @@ -28,11 +28,26 @@ import { import { AnalysisBuildStatus, AnalysisNodeRunStatus } from "~/types/analysis"; import { ApprovalStatus } from "~/types/node"; import ContainerCounter from "~/components/analysis/ContainerCounter.vue"; -import { useNodeType } from "~/composables/useNodeType"; +import { useDatastoreRequirement } from "~/composables/useDatastoreRequirement"; import type { ModifiedAnalysisNode } from "~/services/modifiedApiInterfaces"; const toast = useToast(); -const nodeType = await useNodeType(); + +// Data Store Requirement Check +const { datastoreState } = await useDatastoreRequirement(); +const datastoreRequired = computed( + () => datastoreState.value.datastoreRequired, +); +const nodeType = computed(() => datastoreState.value.nodeType); + +const datastoreBadgeSeverity = computed(() => + datastoreRequired.value ? "danger" : "secondary", +); +const datastoreBadgeTooltip = computed(() => + datastoreRequired.value + ? "Data store missing!" + : "Data store missing, but not required", +); const analysesMap = ref>(new Map()); const analyses = computed(() => Array.from(analysesMap.value.values())); @@ -641,9 +656,11 @@ const onCloseNavToast = () => { >
- @@ -725,7 +742,7 @@ const onCloseNavToast = () => { :analysisRunStatus="slotProps.data.run_status" :datastore="slotProps.data.datastore" :nodeId="slotProps.data.node_id" - :nodeType="nodeType!" + :requireDatastore="datastoreRequired!" :projectId="slotProps.data.analysis.project_id" @missingDataStore="showDataStoreNavToast" @updateAnalysisRow="updateAnalysisRun" diff --git a/components/analysis/AnalysisControlButtons.vue b/components/analysis/AnalysisControlButtons.vue index bb893681..ab71484f 100644 --- a/components/analysis/AnalysisControlButtons.vue +++ b/components/analysis/AnalysisControlButtons.vue @@ -40,8 +40,8 @@ const props = defineProps({ type: Boolean, required: true, }, - nodeType: { - type: String, + requireDatastore: { + type: Boolean, required: true, }, }); @@ -274,7 +274,7 @@ async function onDeleteAnalysis() { :disabled=" !buttonStatuses.playActive || !(props.analysisBuildStatus === AnalysisBuildStatus.Finished) || - (!props.datastore && props.nodeType != 'aggregator') + (!props.datastore && props.requireDatastore) " :loading="loading" aria-label="Start" @@ -289,7 +289,7 @@ async function onDeleteAnalysis() { :disabled=" !buttonStatuses.rerunActive || !(props.analysisBuildStatus === AnalysisBuildStatus.Finished) || - (!props.datastore && props.nodeType != 'aggregator') + (!props.datastore && props.requireDatastore) " :loading="loading" aria-label="Rerun" @@ -303,7 +303,7 @@ async function onDeleteAnalysis() { :disabled=" !buttonStatuses.stopActive || !(props.analysisBuildStatus === AnalysisBuildStatus.Finished) || - (!props.datastore && props.nodeType != 'aggregator') + (!props.datastore && props.requireDatastore) " :loading="loading" aria-label="Stop" @@ -317,7 +317,7 @@ async function onDeleteAnalysis() { :disabled=" !buttonStatuses.deleteActive || !(props.analysisBuildStatus === AnalysisBuildStatus.Finished) || - (!props.datastore && props.nodeType != 'aggregator') + (!props.datastore && props.requireDatastore) " :loading="loading" aria-label="Delete" diff --git a/components/events/EventViewer.vue b/components/events/EventViewer.vue index 0c0d2628..9d214011 100644 --- a/components/events/EventViewer.vue +++ b/components/events/EventViewer.vue @@ -5,7 +5,7 @@ import SearchBar from "~/components/table/SearchBar.vue"; import { EventLogLevelTag, EventServiceTag, - type EventTag + type EventTag, } from "~/types/eventTag"; import TagFilterSidePanel from "~/components/events/TagFilterSidePanel.vue"; import type { EventLog, EventLogResponse } from "~/services/Api"; @@ -21,21 +21,21 @@ const appliedFilters = ref([]); const logLevelColorMap = new Map([ [EventLogLevelTag.Error, "#ef4444"], [EventLogLevelTag.Warning, "#eab308"], - [EventLogLevelTag.Info, "#3b82f6"] + [EventLogLevelTag.Info, "#3b82f6"], ]); const logLevelDistributions = ref( Array.from(logLevelColorMap, ([label, color]) => ({ label, color, - value: 0 - })) + value: 0, + })), ); const filters = ref(); const dateTimeFormat = new Intl.DateTimeFormat(undefined, { dateStyle: "short", - timeStyle: "long" + timeStyle: "long", }); const { data: response, status } = await getEvents(); @@ -87,12 +87,16 @@ function formatEventName(eventName: string): string { return eventChunks.join("-"); } -function formatTimestamp(timestamp: string): string { - const date = new Date(timestamp + "Z"); // Python does not return timestamp with standard "Z" - const formattedDateTime = dateTimeFormat.format(date); - const [dateString, timeString] = formattedDateTime.split(", "); +function formatTimestamp(timestamp: string): string | undefined { + try { + const date = new Date(timestamp); // Python does not return timestamp with standard "Z" + const formattedDateTime = dateTimeFormat.format(date); + const [dateString, timeString] = formattedDateTime.split(", "); - return `${dateString}
${timeString}`; + return `${dateString}
${timeString}`; + } catch (error) { + console.error(`Timestamp: ${timestamp}; Error: ${error}`); + } } function getLogLevelColor(tags: string[]): string | undefined { @@ -101,7 +105,7 @@ function getLogLevelColor(tags: string[]): string | undefined { for (const tag of [ EventLogLevelTag.Error, EventLogLevelTag.Warning, - EventLogLevelTag.Info + EventLogLevelTag.Info, ]) { if (tags.includes(tag)) { return logLevelColorMap.get(tag); @@ -127,7 +131,7 @@ FilterService.register("tagsContainsAny", (value, filter) => { const defaultFilters = { global: { value: null, matchMode: FilterMatchMode.CONTAINS }, - "attributes.tags": { value: null, matchMode: "tagsContainsAny" } + "attributes.tags": { value: null, matchMode: "tagsContainsAny" }, }; filters.value = defaultFilters; @@ -140,7 +144,7 @@ function clearAllFilters() { const clearedFilters = {}; for (const filterKey in defaultFilters) { clearedFilters[filterKey] = { - ...defaultFilters[filterKey] + ...defaultFilters[filterKey], }; clearedFilters[filterKey].value = null; } @@ -150,7 +154,7 @@ function clearAllFilters() { function handleRemoveFilterTag(tag: EventTag) { appliedFilters.value = appliedFilters.value.filter( - (filter) => filter !== tag + (filter) => filter !== tag, ); } @@ -175,7 +179,7 @@ watch( filters.value["attributes.tags"].value = null; } }, - { deep: true } + { deep: true }, ); @@ -196,7 +200,7 @@ watch(
FILTERS: ({{ appliedFilters.length }})FILTERS: ({{ appliedFilters.length }})
diff --git a/components/header/AvatarButton.vue b/components/header/AvatarButton.vue index 34ced340..042d7a5d 100644 --- a/components/header/AvatarButton.vue +++ b/components/header/AvatarButton.vue @@ -3,11 +3,18 @@ import Menu from "primevue/menu"; import Button from "primevue/button"; import { useRuntimeConfig } from "#app"; import CleanupDialog from "~/components/header/CleanupDialog.vue"; +import { useDatastoreRequirement } from "~/composables/useDatastoreRequirement"; const { signIn, signOut, status, data } = useAuth(); const menu = ref(); +const { datastoreState, setDatastoreRequired } = + await useDatastoreRequirement(); +const datastoreRequired = computed( + () => datastoreState.value.datastoreRequired, +); + const config = useRuntimeConfig(); const baseUrl = new URL(config.public.baseUrl).origin; const idpProvider = config.public.idpProvider; @@ -28,6 +35,7 @@ const menuItems = ref([ label: userActionLabel, icon: userActionIcon, command: () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions isAuthenticated.value ? signOut() : signIn(`${idpProvider}`); }, }, @@ -45,6 +53,11 @@ const menuItems = ref([ }, disabled: !isAuthenticated.value, }, + { + label: "Require Data Store", + icon: "pi pi-database", + isToggle: true, // custom flag to identify it in the slot + }, ], }, ]); @@ -92,6 +105,22 @@ const toggle = (event) => { >