diff --git a/backend/analysis/api.py b/backend/analysis/api.py index a481fde8c15e60e9fddc84986183242e0679ddd0..984063da787174449a5180e61c4abb70ed61967a 100644 --- a/backend/analysis/api.py +++ b/backend/analysis/api.py @@ -4,6 +4,9 @@ from django.conf import settings from ninja import Router, File from ninja.files import UploadedFile + +from django_to_galaxy.models import GalaxyInstance + from analysis.models import ( Analysis, AnalysisWorkflow, @@ -38,6 +41,14 @@ def csrf(request): return JsonResponse({"token": token}) +@router.get("is-galaxy-online") +def is_galaxy_online(request): + + gi = GalaxyInstance.objects.get(name=settings.GALAXY_INSTANCE_NAME) + + return JsonResponse({"is_online": gi.is_online()}) + + @router.get("login") def login(request): if not request.session.session_key: @@ -49,7 +60,7 @@ def login(request): ) -@router.post("add", response=AnalysisOutSchema) +@router.post("add", response={200: AnalysisOutSchema, 503: Error}) def add( request, genomefile: UploadedFile = File(...), @@ -71,6 +82,12 @@ def add( session = Session.objects.get(session_key=session_key) aw = AnalysisWorkflow.objects.get(galaxy_id=settings.GALAXY_WORKFLOW_ID) + + # check if Galaxy online + + is_galaxy_online = aw.analysis_owner.galaxy_instance.is_online() + if not is_galaxy_online: + return 503, {"message": "The Galaxy instance is offline"} input_files = [ genome_path, ] @@ -80,11 +97,16 @@ def add( return analysis -@router.get("/", response=list[AnalysisOutSchema]) +@router.get("/", response={200: list[AnalysisOutSchema], 503: Error}) def list(request): session_key = request.session.session_key if session_key: session = Session.objects.get(session_key=session_key) + gi = GalaxyInstance.objects.get(name=settings.GALAXY_INSTANCE_NAME) + # check if Galaxy online + is_galaxy_online = gi.is_online() + if not is_galaxy_online: + return 503, {"message": "The Galaxy instance is offline"} return Analysis.objects.filter(analysis_history__session=session) else: return [] diff --git a/backend/analysis/models.py b/backend/analysis/models.py index 6418f169d3d029f4d7224829e59fbcb0c4caacf1..3793948d213a390958c31a64f926e33ea4aaf1c2 100644 --- a/backend/analysis/models.py +++ b/backend/analysis/models.py @@ -2,16 +2,14 @@ import csv import tarfile from typing import Any from django.db import models -from django_to_galaxy.utils import enabled_cache from django.contrib.sessions.models import Session from tempfile import mkstemp from django.conf import settings from Bio import SeqIO from pathlib import Path from rich.console import Console -from typing import List, Dict +from typing import Dict -console = Console() # from galaxy.models import WorkflowInvocation @@ -31,6 +29,8 @@ from .schemas import GeneEntry, HmmerEntry, ProteinEntry, SystemEntry # Create your models here. +console = Console() + class AnalysisGalaxyUser(GalaxyUser): def create_history(self, session, name=None): diff --git a/deploy/charts/nuxt/templates/deployment.yaml b/deploy/charts/nuxt/templates/deployment.yaml index ca6d2502ae6a6432d4d1f556f8bf626a4b05fb30..38819ab1b156572aa4d92a0df66981b5c272dbc1 100644 --- a/deploy/charts/nuxt/templates/deployment.yaml +++ b/deploy/charts/nuxt/templates/deployment.yaml @@ -48,11 +48,11 @@ spec: value: {{ .Values.dfApiPrefix }} livenessProbe: httpGet: - path: / + path: /api/probe port: 3000 readinessProbe: httpGet: - path: / + path: /api/probe port: 3000 resources: {{- toYaml .Values.resources | nindent 12 }} diff --git a/frontend/components/AnalysisList.vue b/frontend/components/AnalysisList.vue index 8180e81320300f30a689e1f656826953555c88fc..8ec51587c5ec51aae623ec4c9451a8385ec7b3c7 100644 --- a/frontend/components/AnalysisList.vue +++ b/frontend/components/AnalysisList.vue @@ -3,19 +3,13 @@ import { computed } from 'vue' import { usePolling } from "../composables/useFetchAnalyses"; import type { Analysis } from '~/types'; import { joinURL } from "ufo"; -// import { useIsGalaxyOffline } from '~/composables/isGalaxyOffline'; const runtimeConfig = useRuntimeConfig() const sessionExpiryDate = useState('sessionExpiryDate') const toEdit: Ref<number | null> = ref(null) const newTitle: Ref<string | null> = ref(null) - - - const sanitizedAnalyses = computed(() => { return data !== null ? data.value?.map(a => { - const createTimeDate = new Date(a.create_time) - return { ...a, create_time: createTimeDate.toLocaleString() @@ -29,13 +23,17 @@ function getResultArchiveUrl(analysisId: number) { async function deleteAnalysis(analysisId: number) { // if (csrfToken.value) { - const { error } = await useAPI<null>(`/analysis/${analysisId}`, - { - method: "DELETE", - } - ) - if (error.value) throw createError(`Cannot delete analysis ${analysisId}`) - refresh() + try { + await useNuxtApp().$api<null>(`/analysis/${analysisId}`, + { + method: "DELETE", + } + ) + await refreshNuxtData() + } catch (error) { + throw createError(`Cannot delete analysis ${analysisId}`) + } + } function editionMode(analysisId: number, name: string) { @@ -44,14 +42,14 @@ function editionMode(analysisId: number, name: string) { } async function editAnalysis(analysisId: number) { // if (csrfToken.value) { - await useAPI<null>(`/analysis/${analysisId}`, + await useNuxtApp().$api<null>(`/analysis/${analysisId}`, { method: "PUT", body: { name: newTitle.value }, onResponse() { - refresh().finally(() => { + refreshNuxtData().finally(() => { toEdit.value = null newTitle.value = null }) @@ -76,27 +74,31 @@ const options = computed(() => { } }) - - - const { data, refresh, pending, error } = await useAPI<Analysis[]>('/analysis/', { key: "analyses-list", }); - usePolling(options) - - if (error.value) { + console.log(error.value) const toValError = toValue(error) - if (toValError !== null) { + + if (toValError?.data) { throw createError({ statusCode: toValError.statusCode, - message: "Error while getting the list of analysis" - + statusMessage: toValError.statusMessage, + message: toValError?.message, + data: toValError?.data ?? "" }) + } else { + console.log(error.value) + throw createError("Unable to get the analysis") + } + + + // } } diff --git a/frontend/components/ErrorAlert.vue b/frontend/components/ErrorAlert.vue new file mode 100644 index 0000000000000000000000000000000000000000..582fd1d5e9cd3f9037ca4dad153dfb21e0ff3267 --- /dev/null +++ b/frontend/components/ErrorAlert.vue @@ -0,0 +1,36 @@ +<script setup lang="ts"> +import { useIsGalaxyOffline } from '~/composables/isGalaxyOffline'; +import type { FetchError } from "ofetch" + +defineEmits(['clearError']) + +export interface Props { + error?: MaybeRef<Error | FetchError | null | undefined> +} +const props = withDefaults(defineProps<Props>(), { + error: undefined, +}); + +const { message, isGalaxyOffline } = useIsGalaxyOffline({ error: props.error }) + + + +</script> + +<template> + <v-alert type="error" variant="tonal" prominent> + <v-card flat color="transparent"> + <v-card-text v-if="isGalaxyOffline"> + {{ message }} + </v-card-text> + <v-card-text v-else> + {{ error }} + </v-card-text> + <v-card-actions> + <v-btn color="currentColor" prepend-icon="md:home" variant="text" @click="$emit('clearError')"> + Back home + </v-btn> + </v-card-actions> + </v-card> + </v-alert> +</template> \ No newline at end of file diff --git a/frontend/components/Main.vue b/frontend/components/Main.vue index 47949bd61d0b77e706eb4067e19a3682a0afde63..9d21eae5a411ee7647f049fe46db25b643910019 100644 --- a/frontend/components/Main.vue +++ b/frontend/components/Main.vue @@ -8,8 +8,6 @@ export interface Props { const props = withDefaults(defineProps<Props>(), { fluid: true }); -const sessionExpiryDate = useState('sessionExpiryDate', () => "") -// const { csrfToken } = useCsrfToken() const maxWidth = ref(1300) const minWidth = ref(900) const computedMinWidth = computed(() => { diff --git a/frontend/components/SubmitAnalysis.vue b/frontend/components/SubmitAnalysis.vue index f50afc01afbe6822361bcde0fff155ac6b8f39fe..b538d0f13f7b7117c65e013d152a86a661b19003 100644 --- a/frontend/components/SubmitAnalysis.vue +++ b/frontend/components/SubmitAnalysis.vue @@ -14,6 +14,14 @@ const { meta, handleSubmit } = useForm({ const files = useField("files"); +const { data, error } = await useAPI<{ is_online: boolean }>("/analysis/is-galaxy-online", { + onRequest({ request }) { + } +}) + +if (data.value !== null && !data.value.is_online) { + throw createError("The galaxy instance is offline") +} </script> <template> diff --git a/frontend/components/UppyGenomeUpload.vue b/frontend/components/UppyGenomeUpload.vue index 76d380c429809dc32da28d373df13b5a4a2f0e3e..848b5b9176efb069be658d09a96e4d62fe86c999 100644 --- a/frontend/components/UppyGenomeUpload.vue +++ b/frontend/components/UppyGenomeUpload.vue @@ -53,10 +53,10 @@ const uppyCovar = new Uppy({ const emitObject = (files.length === 0) ? undefined : files emit('update:modelValue', emitObject) }) - .on("error", (error) => { - console.log(error) - throw createError("uppy error") - }); + // .on("error", (error) => { + // console.log(error) + // throw createError("uppy error") + // }); const themeUppy = computed(() => { diff --git a/frontend/composables/isGalaxyOffline.ts b/frontend/composables/isGalaxyOffline.ts new file mode 100644 index 0000000000000000000000000000000000000000..6dbb86a8c80c2f3e7127d882a9007e701e3a3fc6 --- /dev/null +++ b/frontend/composables/isGalaxyOffline.ts @@ -0,0 +1,18 @@ +import type { FetchError } from "ofetch" + +export const useIsGalaxyOffline = ({ error }: { + error: MaybeRef<Error | FetchError | null | undefined> +}) => { + const message = ref<string>("The Galaxy instance used to run the analyses is currently offline. It is probably a maintenance ") + const isGalaxyOffline = ref<boolean>(false) + const toValError = toValue(error) + console.dir(toValError) + if (toValError && "data" in toValError) { + if (toValError?.statusCode === 503 && toValError.data?.message === "The Galaxy instance is offline") { + isGalaxyOffline.value = true + } + + } + + return { message, isGalaxyOffline } +} diff --git a/frontend/composables/useFetchAnalyses.ts b/frontend/composables/useFetchAnalyses.ts index ee4ee5cf200ba44b92caa947c69f9980213055aa..bfac4b218dbefcbe7c358dd0ddc0acd65faf412e 100644 --- a/frontend/composables/useFetchAnalyses.ts +++ b/frontend/composables/useFetchAnalyses.ts @@ -1,18 +1,13 @@ import { useIntervalFn } from "@vueuse/core"; import { NON_TERMINAL_STATE, type JobState, type AnalysesPollingOptions, type Analysis } from "~/types"; - - - - -export function usePolling( options: MaybeRef<AnalysesPollingOptions> = { interval: 5000, refresh: null, data: ref(null), pending: ref(false) }) { +export function usePolling(options: MaybeRef<AnalysesPollingOptions> = { interval: 5000, refresh: null, data: ref(null), pending: ref(false) }) { const nonTerminalState = computed(() => { return new Set<JobState>(NON_TERMINAL_STATE) }) const { interval, refresh, pending, data } = toValue(options) const { pause, resume, isActive } = useIntervalFn(() => { - console.log("je suis dans le polling") if (!pending.value) { console.log( `pending ${pending.value diff --git a/frontend/error.vue b/frontend/error.vue index a42e8255897f17089697752a4b767826e7fa8c2e..645b210a33456a22a12d8acf0e171c9d73e42473 100644 --- a/frontend/error.vue +++ b/frontend/error.vue @@ -1,4 +1,5 @@ <script setup lang="ts"> + const error = useError() function handleError() { clearError({ redirect: '/' }) @@ -7,6 +8,7 @@ function handleError() { <template> <div> <NuxtLayout> + <!-- <div v-if="isGalaxyOffline">galaxy offfline</div> --> <v-alert type="error" variant="tonal" prominent> <v-card flat color="transparent"> <v-card-title v-if="error && error?.statusCode"> diff --git a/frontend/middleware/maintenance.global.ts b/frontend/middleware/maintenance.global.ts index 4e10edfe03eb1b39cb088ae02234233dc5cf6285..e1469c6ef87749d1e4153faa2125db17e7a5ff3c 100644 --- a/frontend/middleware/maintenance.global.ts +++ b/frontend/middleware/maintenance.global.ts @@ -7,10 +7,4 @@ export default defineNuxtRouteMiddleware((to, from) => { if (isMaintenance && to.name !== "maintenance") { return navigateTo('/maintenance') } - // else { return } - - - - - }) diff --git a/frontend/pages/analyses/index.vue b/frontend/pages/analyses/index.vue index beda58bb92e3ff107d9a599de16137b8584ccb94..dcf62b3a354e41a99d9db9db16e0db56897ee9d5 100644 --- a/frontend/pages/analyses/index.vue +++ b/frontend/pages/analyses/index.vue @@ -27,19 +27,9 @@ function resetError(error) { <v-card flat color="transparent"> <v-breadcrumbs :items="breadcrumbItems"></v-breadcrumbs> <NuxtErrorBoundary> - <analysis-list> </analysis-list> + <AnalysisList></AnalysisList> <template #error="{ error }"> - <v-alert type="error" variant="tonal" prominent> - <v-card flat color="transparent"> - <v-card-text> - {{ error }} - </v-card-text> - <v-card-actions> - <v-btn color="currentColor" prepend-icon="md:home" variant="outlined" - @click="resetError(error)">Back home</v-btn> - </v-card-actions> - </v-card> - </v-alert> + <ErrorAlert :error="error" @clear-error="resetError(error)"></ErrorAlert> </template> </NuxtErrorBoundary> </v-card> diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index b9ccb96a343a2c087c4133d36592a7195a51d331..82f4e13d1d5886da3720c153520338b9dd8c7fef 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -82,17 +82,8 @@ function resetError(error) { <NuxtErrorBoundary> <SubmitAnalysis></SubmitAnalysis> <template #error="{ error }"> - <v-alert type="error" variant="tonal" prominent> - <v-card flat color="transparent"> - <v-card-text> - {{ error }} - </v-card-text> - <v-card-actions> - <v-btn color="currentColor" prepend-icon="md:home" variant="outlined" @click="resetError(error)">Back - home</v-btn> - </v-card-actions> - </v-card> - </v-alert></template> + <ErrorAlert :error="error"></ErrorAlert> + </template> </NuxtErrorBoundary> </v-card> </v-col> diff --git a/frontend/plugins/api.ts b/frontend/plugins/api.ts index 8d66b65a1eeaca3a277ee8be7258bb7cb85125da..57f79643d56291db16ec135786b0ffd419bc1079 100644 --- a/frontend/plugins/api.ts +++ b/frontend/plugins/api.ts @@ -5,6 +5,7 @@ export default defineNuxtPlugin(() => { const $api = $fetch.create({ baseURL: "/dfapi", async onRequest({ request, options, error }) { + console.log("dans le on request du create") const { data } = await useFetch("/api/auth", { headers }) const toValToken = toValue(csrfToken) if (toValToken !== undefined) { diff --git a/frontend/server/api/probe.get.ts b/frontend/server/api/probe.get.ts new file mode 100644 index 0000000000000000000000000000000000000000..a5cd75efb2203b5404c112c9de13ed4c2c7e7a56 --- /dev/null +++ b/frontend/server/api/probe.get.ts @@ -0,0 +1,3 @@ +export default defineEventHandler((event) => { + return 'ok' +})