diff --git a/dashboard/pkg/epinio/components/application/AppInfo.vue b/dashboard/pkg/epinio/components/application/AppInfo.vue index 61b74836..c2b27220 100644 --- a/dashboard/pkg/epinio/components/application/AppInfo.vue +++ b/dashboard/pkg/epinio/components/application/AppInfo.vue @@ -144,7 +144,7 @@ onMounted(() => { chart: moveBooleansToFront(props.application.chart?.settings) || {}, configuration: { configurations: props.application.configuration?.configurations || [], - instances: props.application.configuration?.instances || 1, + instances: props.application.configuration?.instances ?? 1, environment: props.application.configuration?.environment || {}, settings: props.application.configuration?.settings || {}, routes: props.application.configuration?.routes || [], diff --git a/dashboard/pkg/epinio/components/application/AppSource.vue b/dashboard/pkg/epinio/components/application/AppSource.vue index 35e06a5e..0eb7c20a 100644 --- a/dashboard/pkg/epinio/components/application/AppSource.vue +++ b/dashboard/pkg/epinio/components/application/AppSource.vue @@ -260,7 +260,7 @@ function onManifestFileSelected(file: string) { }, configuration: { configurations: parsed.configuration?.configurations || [], - instances: parsed.configuration.instances || 1, + instances: parsed.configuration.instances ?? 1, environment: parsed.configuration.environment || {}, settings: parsed.configuration?.settings || {}, routes: parsed.configuration.routes || [] diff --git a/dashboard/pkg/epinio/edit/applications.vue b/dashboard/pkg/epinio/edit/applications.vue index c3fdb3b9..06cec00f 100644 --- a/dashboard/pkg/epinio/edit/applications.vue +++ b/dashboard/pkg/epinio/edit/applications.vue @@ -100,7 +100,18 @@ const canEditConfig = computed(() => { // If the user lacks config write perms, never show the primary "Edit Config" footer button, // regardless of how this dialog was opened (view or edit route). const hideEditConfigButton = computed(() => !canEditConfig.value); -const validationPassed = computed(() => !Object.values(tabErrors).find((error) => error)); +const validationPassed = computed(() => { + const tabsValid = !Object.values(tabErrors).find((error) => error); + return tabsValid; +}); + +const shouldRestartOnSave = computed(() => { + const previousInstances = Number(props.initialValue?.configuration?.instances ?? props.initialValue?.desiredInstances ?? 0); + const nextInstances = Number(props.value?.configuration?.instances ?? props.value?.desiredInstances ?? 0); + const instancesChanged = previousInstances !== nextInstances; + + return props.value?.canRestartAfterConfigSave || instancesChanged; +}); const done = () => { if (!doneRoute) { @@ -117,7 +128,7 @@ const done = () => { async function save(saveCb: (success: boolean) => void) { errors.value = []; try { - await props.value.update(); + await props.value.update({ restart: shouldRestartOnSave.value }); await props.value.updateConfigurations( props.initialValue.baseConfigurationsNames || [], diff --git a/dashboard/pkg/epinio/models/applications.js b/dashboard/pkg/epinio/models/applications.js index 3cd47197..6a48578f 100644 --- a/dashboard/pkg/epinio/models/applications.js +++ b/dashboard/pkg/epinio/models/applications.js @@ -117,6 +117,10 @@ export default class EpinioApplicationModel extends EpinioNamespacedResource { return !!(base && canEditConfig); } + get canRestartAfterConfigSave() { + return this.status === STATES.RUNNING && !!this.image_url; + } + get details() { const res = []; diff --git a/dashboard/pkg/epinio/utils/errors.ts b/dashboard/pkg/epinio/utils/errors.ts index 03fc78c9..f11d09d3 100644 --- a/dashboard/pkg/epinio/utils/errors.ts +++ b/dashboard/pkg/epinio/utils/errors.ts @@ -1,26 +1,47 @@ import { isArray } from '@shell/utils/array'; export function epinioExceptionToErrorsArray(err: any): any { - if (err?.errors?.length === 1) { - return epinioExceptionToErrorsArray(err?.errors[0]); - } + const formatError = (item: any) => { + if (!item) { + return ''; + } + + if (typeof item === 'string') { + return item; + } - if ( err?.response?.data ) { - const body = err.response.data; + const status = item.status ? `[${ item.status }] ` : ''; + const title = item.title || item.message || ''; + const details = item.details || item.detail || ''; - if ( body && body.message ) { - return [body.message]; - } else { - return [err]; + return `${ status }${ title }${ details ? ` - ${ details }` : '' }`.trim(); + }; + + const normalize = (input: any): string[] => { + if (!input) { + return ['Unknown error']; + } + + if (isArray(input)) { + return input.map(formatError).filter(Boolean); } - } else if (err.status && err.title) { - const title = err.title; - const detail = err.detail ? ` - ${ err.detail }` : ''; - - return [`${ title }${ detail }`]; - } else if ( isArray(err) ) { - return err; - } else { - return [err]; + + if (input?.errors && isArray(input.errors)) { + return input.errors.map(formatError).filter(Boolean); + } + + if (input?.message) { + return [input.message]; + } + + const formatted = formatError(input); + + return formatted ? [formatted] : ['Unknown error']; + }; + + if (err?.response?.data) { + return normalize(err.response.data); } + + return normalize(err); }