fix: surface actual error message when service installation fails

Backend returned { error: message } on 400 but frontend expected { message }.
catchInternal swallowed Axios errors and returned undefined, causing a
generic 'An internal error occurred' message instead of the real reason
(already installed, already in progress, not found).

- Fix 400 response shape to { success: false, message } in controller
- Replace catchInternal with direct error handling in installService,
  affectService, and forceReinstallService API methods
- Extract error.response.data.message from Axios errors so callers
  see the actual server message
This commit is contained in:
Tom Boucher 2026-03-14 20:56:36 -04:00 committed by Jake Turner
parent 4642dee6ce
commit 6b558531be
2 changed files with 26 additions and 8 deletions

View File

@ -35,7 +35,7 @@ export default class SystemController {
if (result.success) { if (result.success) {
response.send({ success: true, message: result.message }); response.send({ success: true, message: result.message });
} else { } else {
response.status(400).send({ error: result.message }); response.status(400).send({ success: false, message: result.message });
} }
} }

View File

@ -1,4 +1,4 @@
import axios, { AxiosInstance } from 'axios' import axios, { AxiosError, AxiosInstance } from 'axios'
import { ListRemoteZimFilesResponse, ListZimFilesResponse } from '../../types/zim' import { ListRemoteZimFilesResponse, ListZimFilesResponse } from '../../types/zim'
import { ServiceSlim } from '../../types/services' import { ServiceSlim } from '../../types/services'
import { FileEntry } from '../../types/files' import { FileEntry } from '../../types/files'
@ -25,13 +25,19 @@ class API {
} }
async affectService(service_name: string, action: 'start' | 'stop' | 'restart') { async affectService(service_name: string, action: 'start' | 'stop' | 'restart') {
return catchInternal(async () => { try {
const response = await this.client.post<{ success: boolean; message: string }>( const response = await this.client.post<{ success: boolean; message: string }>(
'/system/services/affect', '/system/services/affect',
{ service_name, action } { service_name, action }
) )
return response.data return response.data
})() } catch (error) {
if (error instanceof AxiosError && error.response?.data?.message) {
return { success: false, message: error.response.data.message }
}
console.error('Error affecting service:', error)
return undefined
}
} }
async checkLatestVersion(force: boolean = false) { async checkLatestVersion(force: boolean = false) {
@ -192,13 +198,19 @@ class API {
} }
async forceReinstallService(service_name: string) { async forceReinstallService(service_name: string) {
return catchInternal(async () => { try {
const response = await this.client.post<{ success: boolean; message: string }>( const response = await this.client.post<{ success: boolean; message: string }>(
`/system/services/force-reinstall`, `/system/services/force-reinstall`,
{ service_name } { service_name }
) )
return response.data return response.data
})() } catch (error) {
if (error instanceof AxiosError && error.response?.data?.message) {
return { success: false, message: error.response.data.message }
}
console.error('Error force reinstalling service:', error)
return undefined
}
} }
async getChatSuggestions(signal?: AbortSignal) { async getChatSuggestions(signal?: AbortSignal) {
@ -459,13 +471,19 @@ class API {
} }
async installService(service_name: string) { async installService(service_name: string) {
return catchInternal(async () => { try {
const response = await this.client.post<{ success: boolean; message: string }>( const response = await this.client.post<{ success: boolean; message: string }>(
'/system/services/install', '/system/services/install',
{ service_name } { service_name }
) )
return response.data return response.data
})() } catch (error) {
if (error instanceof AxiosError && error.response?.data?.message) {
return { success: false, message: error.response.data.message }
}
console.error('Error installing service:', error)
return undefined
}
} }
async listCuratedMapCollections() { async listCuratedMapCollections() {