@@ -104,7 +104,8 @@ const Badge = styled(Box)<{ $badgeColors: { primary: string, secondary: string }
104104
105105 .instance-name {
106106 font-size: 1.6rem;
107- display: inline;
107+ display: inline-flex;
108+ align-items: center;
108109 vertical-align: middle;
109110 text-overflow: ellipsis;
110111 overflow: hidden;
@@ -162,6 +163,15 @@ const EnterpriseBadge = styled.span`
162163 }
163164`
164165
166+ const Separator = styled . span < { $color : string } > `
167+ display: inline-block;
168+ width: 0.15rem;
169+ margin: 0 1rem;
170+ height: 1.8rem;
171+ background: ${ ( { $color } ) => $color } ;
172+
173+ `
174+
165175const getSecondaryBadgeColor = ( primaryColor : string | null , theme ?: any ) : string => {
166176 if ( ! primaryColor || ! primaryColor . startsWith ( 'rgb' ) ) {
167177 return theme ?. color . foreground || "inherit" ;
@@ -237,25 +247,17 @@ const useBadgeColors = (instance_rgb: string | null) => {
237247 }
238248}
239249
240- const EnvironmentIcon = ( { instanceType, color, background } : { instanceType : InstanceType | undefined , color ?: string , background ?: string } ) => {
241- const getIcon = ( ) => {
242- switch ( instanceType ) {
243- case "development" :
244- return < Tools size = "18px" color = { color } />
245- case "production" :
246- return < RocketTakeoff size = "18px" color = { color } />
247- case "testing" :
248- return < Flask size = "18px" color = { color } style = { { transform : 'scale(1.2)' } } />
249- default :
250- return < InfoCircle size = "18px" style = { { transform : 'translateY(-0.2rem)' } } color = { color } />
251- }
250+ const EnvironmentIcon = ( { instanceType, color } : { instanceType : InstanceType | undefined , color ?: string } ) => {
251+ switch ( instanceType ) {
252+ case "development" :
253+ return < Tools size = "18px" color = { color } />
254+ case "production" :
255+ return < RocketTakeoff size = "18px" color = { color } />
256+ case "testing" :
257+ return < Flask size = "18px" color = { color } style = { { transform : 'scale(1.2)' } } />
258+ default :
259+ return < InfoCircle size = "18px" style = { { transform : 'translateY(-0.2rem)' } } color = { color } />
252260 }
253-
254- return (
255- < EnvIconWrapper $background = { background } >
256- { getIcon ( ) }
257- </ EnvIconWrapper >
258- )
259261} ;
260262
261263const CustomIconWithTooltip = ( {
@@ -278,7 +280,9 @@ const CustomIconWithTooltip = ({
278280 < FlexCol >
279281 { shownValues ?. instance_type && (
280282 < Title >
281- < EnvironmentIcon color = { badgeColors . secondary } background = { badgeColors . primary } instanceType = { shownValues ?. instance_type } />
283+ < EnvIconWrapper $background = { badgeColors . primary } >
284+ < EnvironmentIcon color = { badgeColors . secondary } instanceType = { shownValues ?. instance_type } />
285+ </ EnvIconWrapper >
282286 < Text color = "foreground" weight = { 400 } > You are connected to a QuestDB instance for { shownValues ?. instance_type } </ Text >
283287 </ Title >
284288 ) }
@@ -308,7 +312,12 @@ export const Toolbar = () => {
308312 const [ currentUser , setCurrentUser ] = useState < string | null > ( null )
309313 const [ settingsPopperActive , setSettingsPopperActive ] = useState ( false )
310314 const [ previewValues , setPreviewValues ] = useState < Preferences | null > ( null )
315+ const [ canEditInstanceName , setCanEditInstanceName ] = useState ( false )
316+ const [ saveError , setSaveError ] = useState < string | null > ( null )
311317 const shownValues = settingsPopperActive ? previewValues : preferences
318+ const instanceTypeReadable = shownValues ?. instance_type
319+ ? shownValues . instance_type . charAt ( 0 ) . toUpperCase ( ) + shownValues . instance_type . slice ( 1 )
320+ : ''
312321 const badgeColors = useBadgeColors ( shownValues ?. instance_rgb ?? null )
313322 const theme = useTheme ( )
314323
@@ -329,14 +338,33 @@ export const Toolbar = () => {
329338 if ( authPayload && currentUser && settings [ "acl.oidc.client.id" ] ) {
330339 setSSOUserNameWithClientID ( settings [ "acl.oidc.client.id" ] , currentUser )
331340 }
341+ return currentUser
332342 }
343+ return null
333344 } catch ( e ) {
345+ return null
346+ }
347+ }
348+
349+ const fetchEditSettingsPermission = async ( currentUser : string | null ) => {
350+ if ( ! currentUser ) {
351+ setCanEditInstanceName ( false )
334352 return
335353 }
354+
355+ try {
356+ const response = await quest . showPermissions ( currentUser )
357+ // Admin user has no permissions listed
358+ const canEdit = response . type === QuestDB . Type . DQL
359+ && ( response . count === 0 || response . data . some ( d => d . permission === 'SETTINGS' ) )
360+ setCanEditInstanceName ( canEdit )
361+ } catch ( e ) {
362+ setCanEditInstanceName ( false )
363+ }
336364 }
337365
338366 useEffect ( ( ) => {
339- fetchServerDetails ( )
367+ fetchServerDetails ( ) . then ( fetchEditSettingsPermission )
340368 refreshSettingsAndPreferences ( )
341369 } , [ ] )
342370
@@ -349,16 +377,23 @@ export const Toolbar = () => {
349377
350378 const handleSaveSettings = async ( values : Preferences ) => {
351379 try {
380+ setSaveError ( null )
352381 await quest . savePreferences ( values )
382+ handleToggle ( false )
353383 } catch ( e ) {
354- // Handle error
384+ console . error ( e )
385+ setSaveError ( `Failed to save instance settings: ${ e instanceof Error ? e . message : e } ` )
386+ } finally {
387+ await refreshSettingsAndPreferences ( )
355388 }
356- await refreshSettingsAndPreferences ( )
357389 }
358390
359391 const handleToggle = useCallback ( ( active : boolean ) => {
360392 setSettingsPopperActive ( active )
361393 setPreviewValues ( active ? preferences : null )
394+ if ( ! active ) {
395+ setSaveError ( null )
396+ }
362397 } , [ preferences ] )
363398
364399 return (
@@ -378,10 +413,10 @@ export const Toolbar = () => {
378413 $badgeColors = { badgeColors }
379414 data-hook = "topbar-instance-badge"
380415 >
381- < Box style = { { padding : '0.7rem' } } >
416+ < Box >
382417 { ( shownValues ?. instance_type ) ? (
383418 < CustomIconWithTooltip
384- icon = { < div data-hook = "topbar-instance-icon" > < EnvironmentIcon instanceType = { shownValues ?. instance_type } color = { badgeColors . secondary } background = { badgeColors . primary } /> </ div > }
419+ icon = { < div data-hook = "topbar-instance-icon" style = { { padding : '0.7rem' } } > < EnvironmentIcon instanceType = { shownValues ?. instance_type } color = { badgeColors . secondary } /> </ div > }
385420 placement = "bottom"
386421 shownValues = { shownValues }
387422 />
@@ -391,22 +426,23 @@ export const Toolbar = () => {
391426 </ Box >
392427 { shownValues ?. instance_name
393428 ? < Text data-hook = "topbar-instance-name" className = "instance-name" >
394- { shownValues ?. instance_type
395- ? `${ shownValues ?. instance_type . charAt ( 0 ) . toUpperCase ( ) } ${ shownValues ?. instance_type . slice ( 1 ) } | `
396- : '' }
429+ { instanceTypeReadable }
430+ < Separator $color = { badgeColors . secondary } />
397431 { shownValues ?. instance_name }
398432 </ Text >
399433 : < Text data-hook = "topbar-instance-name" className = "instance-name placeholder" > Instance name is not set</ Text >
400434 }
401-
402- < InstanceSettingsPopper
403- active = { settingsPopperActive }
404- onToggle = { handleToggle }
405- values = { previewValues ?? preferences }
406- onSave = { handleSaveSettings }
407- onValuesChange = { setPreviewValues }
408- trigger = { < Edit data-hook = "topbar-instance-edit-icon" size = "18px" className = { `edit-icon ${ shownValues ?. instance_name ? '' : 'placeholder' } ` } /> }
409- />
435+ { canEditInstanceName && (
436+ < InstanceSettingsPopper
437+ active = { settingsPopperActive }
438+ onToggle = { handleToggle }
439+ values = { previewValues ?? preferences }
440+ onSave = { handleSaveSettings }
441+ onValuesChange = { setPreviewValues }
442+ error = { saveError }
443+ trigger = { < Edit data-hook = "topbar-instance-edit-icon" size = "18px" className = { `edit-icon ${ shownValues ?. instance_name ? '' : 'placeholder' } ` } /> }
444+ />
445+ ) }
410446 </ Badge >
411447 ) }
412448 < Box gap = "0.5rem" >
0 commit comments