@@ -111,7 +111,7 @@ function getItemName(item: any): string {
111111 return item ?.properties ?.name || item ?.properties ?.title || item ?.name || item ?.title || ' '
112112}
113113
114- /** Click a related item → load its detail (if same type) or navigate */
114+ /** Click a related item → navigate directly to its detail view */
115115function viewRelatedItem(link : RelatedResourceLink , item : any ) {
116116 const id = getItemId (item )
117117 if (id === ' —' ) return
@@ -121,13 +121,14 @@ function viewRelatedItem(link: RelatedResourceLink, item: any) {
121121 manualId .value = ' '
122122 fetchDetail (id )
123123 } else {
124- // Different type — navigate to that type 's explorer with the item selected
124+ // Different type — navigate directly to that item 's detail view
125125 router .push ({
126126 path: ` /explore/${link .childType } ` ,
127127 query: {
128128 parentType: props .resourceType ,
129129 parentId: String (detail .value ?.id || props .resourceId ),
130130 relation: link .relation ,
131+ resourceId: id ,
131132 },
132133 })
133134 }
@@ -152,6 +153,62 @@ function toggleRelation(relation: string) {
152153 state .expanded = ! state .expanded
153154}
154155
156+ // ========================================
157+ // Parent navigation (observation → datastream → system, etc.)
158+ // ========================================
159+
160+ interface ParentLink {
161+ label: string
162+ resourceType: string
163+ resourceId: string
164+ icon: string
165+ }
166+
167+ /** Extract navigable parent references from the raw detail JSON cross-reference fields */
168+ const parentLinks = computed <ParentLink []>(() => {
169+ if (! detail .value ) return []
170+ const links: ParentLink [] = []
171+ const raw = detail .value
172+
173+ // system@id (present on datastreams, controlStreams)
174+ if (typeof raw [' system@id' ] === ' string' ) {
175+ links .push ({ label: ' System' , resourceType: ' systems' , resourceId: raw [' system@id' ], icon: ' pi pi-server' })
176+ } else if (raw [' system@link' ]?.uid ) {
177+ // Some servers use system@link with href containing the ID
178+ const match = raw [' system@link' ]?.href ?.match (/ systems\/ ([^ /?] + )/ )
179+ if (match ) links .push ({ label: ' System' , resourceType: ' systems' , resourceId: match [1 ], icon: ' pi pi-server' })
180+ }
181+
182+ // datastream@id (present on observations)
183+ if (typeof raw [' datastream@id' ] === ' string' ) {
184+ links .push ({ label: ' Datastream' , resourceType: ' datastreams' , resourceId: raw [' datastream@id' ], icon: ' pi pi-chart-line' })
185+ }
186+
187+ // controlstream@id (present on commands)
188+ if (typeof raw [' controlstream@id' ] === ' string' ) {
189+ links .push ({ label: ' Control Stream' , resourceType: ' controlStreams' , resourceId: raw [' controlstream@id' ], icon: ' pi pi-sliders-h' })
190+ }
191+
192+ // command@id (present on commandStatuses — future-proofing)
193+ if (typeof raw [' command@id' ] === ' string' ) {
194+ links .push ({ label: ' Command' , resourceType: ' commands' , resourceId: raw [' command@id' ], icon: ' pi pi-send' })
195+ }
196+
197+ // deployment@id (present on deployed systems)
198+ if (typeof raw [' deployment@id' ] === ' string' ) {
199+ links .push ({ label: ' Deployment' , resourceType: ' deployments' , resourceId: raw [' deployment@id' ], icon: ' pi pi-map' })
200+ }
201+
202+ return links
203+ })
204+
205+ function navigateToParent(parent : ParentLink ) {
206+ router .push ({
207+ path: ` /explore/${parent .resourceType } ` ,
208+ query: { resourceId: parent .resourceId },
209+ })
210+ }
211+
155212async function fetchDetail(id ? : string ) {
156213 const useId = id || manualId .value || props .resourceId
157214 if (! useId ) return
@@ -217,6 +274,23 @@ watch(
217274 </div >
218275
219276 <template v-if =" detail " >
277+ <!-- Parent navigation breadcrumbs -->
278+ <div v-if =" parentLinks.length > 0" class =" parent-nav" >
279+ <i class =" pi pi-arrow-up parent-nav-icon" ></i >
280+ <span class =" parent-nav-label" >Parent:</span >
281+ <button
282+ v-for =" parent in parentLinks"
283+ :key =" parent.resourceType"
284+ class =" parent-link"
285+ @click =" navigateToParent(parent)"
286+ >
287+ <i :class =" parent.icon" ></i >
288+ {{ parent.label }}
289+ <code >{{ parent.resourceId }}</code >
290+ <i class =" pi pi-arrow-up-right parent-link-arrow" ></i >
291+ </button >
292+ </div >
293+
220294 <!-- Inline related resource panels in a grid -->
221295 <div v-if =" allRelations.length > 0 && (detail?.id || props.resourceId)" class =" relations-grid" >
222296 <div
@@ -342,6 +416,15 @@ watch(
342416.browse-all-link { display : block ; width : 100% ; padding : 0.3rem 0.65rem ; border : none ; background : transparent ; color : #0369a1 ; font-size : 0.75rem ; font-weight : 600 ; cursor : pointer ; text-align : left ; }
343417.browse-all-link :hover { background : #e0f2fe ; }
344418
419+ /* Parent navigation bar */
420+ .parent-nav { display : flex ; align-items : center ; gap : 0.5rem ; padding : 0.4rem 0.65rem ; background : #fefce8 ; border : 1px solid #fde68a ; border-radius : 6px ; flex-wrap : wrap ; }
421+ .parent-nav-icon { font-size : 0.8rem ; color : #ca8a04 ; }
422+ .parent-nav-label { font-size : 0.78rem ; font-weight : 600 ; color : #92400e ; white-space : nowrap ; }
423+ .parent-link { display : inline-flex ; align-items : center ; gap : 0.3rem ; padding : 0.2rem 0.5rem ; border : 1px solid #fde68a ; border-radius : 4px ; background : #fffbeb ; color : #92400e ; font-size : 0.78rem ; font-weight : 600 ; cursor : pointer ; transition : all 0.15s ; white-space : nowrap ; }
424+ .parent-link :hover { background : #fef3c7 ; border-color : #f59e0b ; }
425+ .parent-link code { font-size : 0.72rem ; background : rgba (0 ,0 ,0 ,0.05 ); padding : 0.05rem 0.25rem ; border-radius : 2px ; max-width : 160px ; overflow : hidden ; text-overflow : ellipsis ; }
426+ .parent-link-arrow { font-size : 0.6rem ; color : #d97706 ; opacity : 0.6 ; }
427+
345428.diagram-details { margin-top : 0.25rem ; }
346429.diagram-summary { cursor : pointer ; font-size : 0.8rem ; font-weight : 600 ; color : #0369a1 ; display : flex ; align-items : center ; gap : 0.35rem ; padding : 0.3rem 0 ; user-select : none ; }
347430.diagram-summary :hover { color : #0284c7 ; }
0 commit comments