1- import { getAllInstalledSolutionsNames , runtimeStarted } from '../node-red/red' ;
1+ import {
2+ getAllInstalledSolutionsNames ,
3+ getAllInstalledSolutionsWithGroups ,
4+ runtimeStarted ,
5+ type InstalledSolutionDetails ,
6+ } from '../node-red/red' ;
7+ import { createKeyringPair } from '../polkadot/account' ;
8+ import { MAIN_CONFIG } from '../config' ;
9+ import { getCachedOperatorAddress } from '../util/operator-address-cache' ;
210
311enum HealthStatus {
412 OK = 'OK' ,
@@ -23,6 +31,33 @@ interface NodeRedHealthStatus extends ComponentHealthStatus {
2331 } ;
2432}
2533
34+ interface SolutionInfo {
35+ id : string ;
36+ name : string ;
37+ status : string ;
38+ installed : boolean ;
39+ }
40+
41+ interface SolutionGroupInfo {
42+ id : string ;
43+ name ?: string ;
44+ hasCidAllowList ?: boolean ;
45+ solutions : SolutionInfo [ ] ;
46+ }
47+
48+ interface OperatorInfo {
49+ address : string ;
50+ subscriptions : string [ ] ;
51+ solutionGroups : SolutionGroupInfo [ ] ;
52+ }
53+
54+ interface SolutionGroupsDetailsStatus {
55+ timestamp : string ;
56+ rpcUrl ?: string ;
57+ workerAddress ?: string ;
58+ operator ?: OperatorInfo ;
59+ }
60+
2661export const isLive = ( ) : ComponentHealthStatus => {
2762 return {
2863 status : HealthStatus . OK ,
@@ -59,3 +94,111 @@ export const getNodeRedHealth = async (): Promise<NodeRedHealthStatus> => {
5994 } ,
6095 } ;
6196} ;
97+
98+ const getOperatorInfo = async ( timeoutMs : number = 5000 ) : Promise < OperatorInfo | undefined > => {
99+ try {
100+ const account = createKeyringPair ( ) ;
101+
102+ // Get installed solutions with groups from Node-RED (local, fast)
103+ let installedSolutionsWithGroups : InstalledSolutionDetails [ ] = [ ] ;
104+ try {
105+ installedSolutionsWithGroups = await getAllInstalledSolutionsWithGroups ( ) ;
106+ } catch {
107+ return undefined ;
108+ }
109+
110+ if ( installedSolutionsWithGroups . length === 0 ) {
111+ // No installed solutions - still need operator address
112+ const operatorAddress = await getCachedOperatorAddress ( account . address , timeoutMs ) ;
113+
114+ if ( operatorAddress == null ) {
115+ return undefined ;
116+ }
117+
118+ return {
119+ address : operatorAddress ,
120+ subscriptions : [ ] ,
121+ solutionGroups : [ ] ,
122+ } ;
123+ }
124+
125+ // Extract unique solution group IDs from installed solutions (local data)
126+ const uniqueSolutionGroupIds = [
127+ ...new Set ( installedSolutionsWithGroups . map ( ( s ) => s . solutionGroupId ) ) ,
128+ ] ;
129+
130+ // Get operator address (required) - getCachedOperatorAddress handles cache and chain query
131+ const operatorAddress = await getCachedOperatorAddress ( account . address , timeoutMs ) ;
132+
133+ if ( operatorAddress == null ) {
134+ return undefined ;
135+ }
136+
137+ // Group installed solutions by solutionGroupId (local data)
138+ const solutionGroupsMap = new Map < string , InstalledSolutionDetails [ ] > ( ) ;
139+ for ( const solution of installedSolutionsWithGroups ) {
140+ const group = solutionGroupsMap . get ( solution . solutionGroupId ) ?? [ ] ;
141+ group . push ( solution ) ;
142+ solutionGroupsMap . set ( solution . solutionGroupId , group ) ;
143+ }
144+
145+ // Build solution groups from local data
146+ const solutionGroups : SolutionGroupInfo [ ] = Array . from ( solutionGroupsMap . entries ( ) ) . map (
147+ ( [ solutionGroupId , solutions ] ) => {
148+ const solutionInfos : SolutionInfo [ ] = solutions . map ( ( solution ) => {
149+ // Extract name from solutionId (format: "name.uuid")
150+ // Example: "newTestGPSaaS.1348d595-ccc4-4a38-85ad-f0e31cc7f410" -> "newTestGPSaaS"
151+ const name = solution . solutionId . includes ( '.' )
152+ ? solution . solutionId . split ( '.' ) . slice ( 0 , - 1 ) . join ( '.' )
153+ : solution . solutionId ;
154+
155+ return {
156+ id : solution . solutionId ,
157+ name,
158+ status : 'Active' , // Assume active if installed
159+ installed : true , // All are installed
160+ } ;
161+ } ) ;
162+
163+ return {
164+ id : solutionGroupId ,
165+ name : solutionGroupId , // Use groupId as name (we don't have name from chain)
166+ solutions : solutionInfos ,
167+ } ;
168+ } ,
169+ ) ;
170+
171+ return {
172+ address : operatorAddress ,
173+ subscriptions : uniqueSolutionGroupIds ,
174+ solutionGroups,
175+ } ;
176+ } catch {
177+ return undefined ;
178+ }
179+ } ;
180+
181+ export const getSolutionGroupsDetailsStatus = async ( ) : Promise < SolutionGroupsDetailsStatus > => {
182+ const timestamp = new Date ( ) . toISOString ( ) ;
183+
184+ // Gather configuration (non-sensitive)
185+ let rpcUrl : string | undefined ;
186+ let workerAddress : string | undefined ;
187+ try {
188+ const account = createKeyringPair ( ) ;
189+ workerAddress = account . address ;
190+ rpcUrl = MAIN_CONFIG . PALLET_RPC_URL ;
191+ } catch {
192+ rpcUrl = MAIN_CONFIG . PALLET_RPC_URL ;
193+ }
194+
195+ // Get operator information with solution groups
196+ const operatorInfo = await getOperatorInfo ( ) ;
197+
198+ return {
199+ timestamp,
200+ rpcUrl,
201+ workerAddress,
202+ operator : operatorInfo ,
203+ } ;
204+ } ;
0 commit comments