117117 <label >Last Checked:</label >
118118 <span >{{ formatDate(provider.lastChecked) }}</span >
119119 </div >
120- <div v-if =" provider.error" class =" provider-detail error-detail" >
121- <label >Error:</label >
122- <span class =" error-message" >{{ provider.error }}</span >
120+
121+ <!-- Enhanced Error Display -->
122+ <div v-if =" provider.error" class =" error-section" >
123+ <div class =" error-header" >
124+ <el-icon class =" error-icon" ><WarningFilled /></el-icon >
125+ <span class =" error-category" >{{ getErrorCategory(provider.error) }}</span >
126+ </div >
127+ <div class =" error-message" >
128+ <strong >Error:</strong > {{ getFormattedError(provider.error) }}
129+ </div >
130+
131+ <!-- Troubleshooting Hints -->
132+ <div class =" troubleshooting-hints" >
133+ <div class =" hint-title" >
134+ <el-icon ><InfoFilled /></el-icon >
135+ <strong >Troubleshooting:</strong >
136+ </div >
137+ <ul class =" hint-list" >
138+ <li v-for =" (hint, index) in getTroubleshootingHints(provider.error)" :key =" index" >
139+ {{ hint }}
140+ </li >
141+ </ul >
142+ </div >
143+
144+ <!-- Retry Button -->
145+ <div class =" retry-section" >
146+ <el-button
147+ type =" primary"
148+ size =" small"
149+ :loading =" retryingProviders.has(provider.name)"
150+ @click =" retryProvider(provider.name)"
151+ >
152+ <el-icon ><Refresh /></el-icon >
153+ {{ retryingProviders.has(provider.name) ? 'Retrying...' : 'Retry Now' }}
154+ </el-button >
155+ <span class =" retry-hint" >Manual retry to reinitialize this provider</span >
156+ </div >
123157 </div >
124158 </div >
125159 </el-card >
164198<script setup lang="ts">
165199import { ref , onMounted } from ' vue'
166200import { ElMessage } from ' element-plus'
167- import { Refresh , InfoFilled } from ' @element-plus/icons-vue'
201+ import { Refresh , InfoFilled , WarningFilled } from ' @element-plus/icons-vue'
168202
169203interface ProviderStatus {
170204 name: string
@@ -195,6 +229,7 @@ const loading = ref(true)
195229const error = ref <string | null >(null )
196230const status = ref <StatusResponse | null >(null )
197231const activeCollapse = ref <string []>([' obpOidc' , ' keycloak' , ' google' , ' github' , ' custom' ])
232+ const retryingProviders = ref <Set <string >>(new Set ())
198233
199234const fetchStatus = async () => {
200235 loading .value = true
@@ -220,6 +255,113 @@ const refreshStatus = async () => {
220255 ElMessage .success (' Provider status refreshed' )
221256}
222257
258+ const retryProvider = async (providerName : string ) => {
259+ retryingProviders .value .add (providerName )
260+
261+ try {
262+ const response = await fetch (` /api/status/providers/${providerName }/retry ` , {
263+ method: ' POST'
264+ })
265+
266+ const result = await response .json ()
267+
268+ if (response .ok && result .success ) {
269+ ElMessage .success (` Provider ${providerName } successfully initialized! ` )
270+ await fetchStatus () // Refresh the status
271+ } else {
272+ ElMessage .error (result .message || ` Failed to retry provider ${providerName } ` )
273+ }
274+ } catch (err ) {
275+ ElMessage .error (` Error retrying provider: ${err instanceof Error ? err .message : ' Unknown error' } ` )
276+ } finally {
277+ retryingProviders .value .delete (providerName )
278+ }
279+ }
280+
281+ const getErrorCategory = (errorMsg : string ): string => {
282+ if (! errorMsg ) return ' Unknown Error'
283+
284+ const msg = errorMsg .toLowerCase ()
285+
286+ if (msg .includes (' http 4' ) || msg .includes (' http 5' )) {
287+ return ' HTTP Error'
288+ } else if (msg .includes (' timeout' ) || msg .includes (' network' ) || msg .includes (' fetch' )) {
289+ return ' Network Error'
290+ } else if (msg .includes (' not configured' ) || msg .includes (' no well-known' )) {
291+ return ' Configuration Error'
292+ } else if (msg .includes (' failed to initialize' )) {
293+ return ' Initialization Error'
294+ } else if (msg .includes (' invalid' ) || msg .includes (' parse' )) {
295+ return ' Validation Error'
296+ }
297+
298+ return ' General Error'
299+ }
300+
301+ const getFormattedError = (errorMsg : string ): string => {
302+ if (! errorMsg ) return ' Unknown error occurred'
303+
304+ // Make common errors more user-friendly
305+ const msg = errorMsg .toLowerCase ()
306+
307+ if (msg .includes (' http 404' )) {
308+ return ' Provider endpoint not found (HTTP 404). The OIDC configuration endpoint is not accessible.'
309+ } else if (msg .includes (' http 401' ) || msg .includes (' http 403' )) {
310+ return ' Access denied (HTTP 401/403). Authentication or authorization required.'
311+ } else if (msg .includes (' http 500' ) || msg .includes (' http 502' ) || msg .includes (' http 503' )) {
312+ return ' Provider server error (HTTP 5xx). The identity provider is experiencing issues.'
313+ } else if (msg .includes (' timeout' )) {
314+ return ' Connection timeout. The provider is not responding in a timely manner.'
315+ } else if (msg .includes (' network' )) {
316+ return ' Network connectivity issue. Cannot reach the provider server.'
317+ } else if (msg .includes (' no well-known url configured' )) {
318+ return ' Missing well-known URL. The provider configuration is incomplete.'
319+ } else if (msg .includes (' failed to initialize' )) {
320+ return ' Initialization failed. Could not create OAuth2 client for this provider.'
321+ }
322+
323+ return errorMsg
324+ }
325+
326+ const getTroubleshootingHints = (errorMsg : string ): string [] => {
327+ if (! errorMsg ) return [' Check server logs for more details' ]
328+
329+ const msg = errorMsg .toLowerCase ()
330+ const hints: string [] = []
331+
332+ if (msg .includes (' http 404' )) {
333+ hints .push (' Verify the provider\' s well-known URL is correct in the OBP API configuration' )
334+ hints .push (' Check that the identity provider is properly deployed and accessible' )
335+ hints .push (' Ensure the OIDC discovery endpoint exists at /.well-known/openid-configuration' )
336+ } else if (msg .includes (' http 401' ) || msg .includes (' http 403' )) {
337+ hints .push (' Check if the provider requires authentication for the discovery endpoint' )
338+ hints .push (' Verify API credentials and permissions' )
339+ } else if (msg .includes (' http 5' )) {
340+ hints .push (' The identity provider may be down or restarting' )
341+ hints .push (' Check the provider\' s status page or logs' )
342+ hints .push (' Try again in a few minutes' )
343+ } else if (msg .includes (' timeout' ) || msg .includes (' network' )) {
344+ hints .push (' Verify network connectivity between API Explorer and the identity provider' )
345+ hints .push (' Check firewall rules and DNS resolution' )
346+ hints .push (' Ensure the provider URL is accessible from the server' )
347+ hints .push (' Try accessing the well-known URL manually from the server' )
348+ } else if (msg .includes (' no well-known' ) || msg .includes (' not configured' )) {
349+ hints .push (' Check environment variables for this provider' )
350+ hints .push (' Verify the provider is configured in the OBP API' )
351+ hints .push (' Review the SETUP_MULTI_PROVIDER.md documentation' )
352+ } else if (msg .includes (' failed to initialize' )) {
353+ hints .push (' Check the provider\' s OIDC configuration is valid' )
354+ hints .push (' Verify client ID and client secret are correct' )
355+ hints .push (' Review server logs for detailed error messages' )
356+ } else {
357+ hints .push (' Check server logs for detailed error information' )
358+ hints .push (' Verify the provider configuration in environment variables' )
359+ hints .push (' Visit the OIDC Debug page for more details' )
360+ }
361+
362+ return hints
363+ }
364+
223365const getProviderDisplayName = (key : string ): string => {
224366 const names: { [key : string ]: string } = {
225367 ' obp-oidc' : ' OBP-OIDC' ,
@@ -412,11 +554,91 @@ h2 {
412554 align-items : flex-start ;
413555}
414556
557+ /* Enhanced Error Section */
558+ .error-section {
559+ background : #fef0f0 ;
560+ padding : 16px ;
561+ border-radius : 6px ;
562+ border-left : 4px solid #f56c6c ;
563+ margin-top : 12px ;
564+ }
565+
566+ .error-header {
567+ display : flex ;
568+ align-items : center ;
569+ gap : 8px ;
570+ margin-bottom : 12px ;
571+ }
572+
573+ .error-icon {
574+ color : #f56c6c ;
575+ font-size : 18px ;
576+ }
577+
578+ .error-category {
579+ font-weight : 600 ;
580+ color : #f56c6c ;
581+ font-size : 14px ;
582+ text-transform : uppercase ;
583+ letter-spacing : 0.5px ;
584+ }
585+
415586.error-message {
587+ background : #fff ;
588+ padding : 10px 12px ;
589+ border-radius : 4px ;
590+ border : 1px solid #fbc4c4 ;
591+ margin-bottom : 12px ;
592+ font-size : 13px ;
593+ line-height : 1.6 ;
594+ color : #303133 ;
595+ }
596+
597+ .error-message strong {
416598 color : #f56c6c ;
417- font-family : ' Courier New' , monospace ;
599+ }
600+
601+ .troubleshooting-hints {
602+ background : #fff ;
603+ padding : 12px ;
604+ border-radius : 4px ;
605+ border : 1px solid #e4e7ed ;
606+ margin-bottom : 12px ;
607+ }
608+
609+ .hint-title {
610+ display : flex ;
611+ align-items : center ;
612+ gap : 6px ;
613+ margin-bottom : 8px ;
614+ color : #409eff ;
615+ font-size : 13px ;
616+ }
617+
618+ .hint-list {
619+ margin : 0 ;
620+ padding-left : 20px ;
621+ color : #606266 ;
622+ font-size : 13px ;
623+ line-height : 1.8 ;
624+ }
625+
626+ .hint-list li {
627+ margin-bottom : 4px ;
628+ }
629+
630+ .retry-section {
631+ display : flex ;
632+ align-items : center ;
633+ gap : 12px ;
634+ padding-top : 8px ;
635+ border-top : 1px solid #fbc4c4 ;
636+ }
637+
638+ .retry-hint {
418639 font-size : 12px ;
419- word-break : break-all ;
640+ color : #909399 ;
641+ font-style : italic ;
420642}
421643
422644/* Environment Config */
0 commit comments