go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers-sdk/v1/resources/docs/src/lrdocs/lrdocs.tsx (about) 1 /** 2 * Copyright (c) Mondoo, Inc. 3 * SPDX-License-Identifier: BUSL-1.1 4 */ 5 6 import * as React from 'react'; 7 import styled, { ThemeProvider } from 'styled-components'; 8 9 import snapshots from '../../static/snapshots.json'; 10 import { JsxElement } from 'typescript'; 11 12 13 type LrArg = { 14 ID: string 15 Type: LrType 16 } 17 18 type LrSimpleArg = { 19 Type: string 20 } 21 22 type LrType = { 23 ListType?: { 24 Type: LrType 25 } 26 MapType?: { 27 Key: LrSimpleArg 28 Value: LrType 29 } 30 SimpleType?: { 31 Type: string 32 } 33 } 34 35 type LrField = { 36 Args?: { 37 List?: LrSimpleArg[] 38 } 39 ID: string 40 Type: LrType 41 updated?: boolean 42 } 43 44 type LrListResource = { 45 Type: { 46 Type: string 47 } 48 } 49 50 type LrInit = { 51 Args: LrArg[] 52 } 53 54 type LrResource = { 55 Body: { 56 Fields: LrField[] | null 57 Inits: LrInit[] | null 58 } 59 ID: string 60 ListType?: LrListResource 61 updated?: boolean 62 } 63 64 type LrSnapshot = { 65 version: string 66 Resources: LrResource[] 67 } 68 69 type LrDocsState = { 70 metadata?: LrSnapshot[] 71 selected?: LrSnapshot 72 } 73 export class LrDocs extends React.Component<{}, LrDocsState> { 74 state: LrDocsState = {} 75 76 render() { 77 if (this.state.metadata == null) { 78 this.load_metadata() 79 return "loading..." 80 } 81 82 let versions = this.state.metadata.map(x => x.version); 83 let { selected } = this.state; 84 85 let idx = versions.indexOf(selected.version); 86 let prev = selected; 87 if (idx <= versions.length-2) { 88 prev = this.state.metadata[idx+1] 89 } 90 91 return ( 92 <SiteStructure> 93 <Versions 94 versions={versions} 95 selected={selected.version} 96 onSelect={(version) => { 97 let selected = this.state.metadata.find(v => v.version == version) 98 this.setState({ 99 selected, 100 }) 101 }} 102 /> 103 <Resources snapshot={selected} prev={prev} /> 104 </SiteStructure> 105 ) 106 } 107 108 load_metadata() { 109 let data = snapshots; 110 let versions = Object.keys(data).sort() 111 112 let metadata = versions.map(v => { 113 let res = data[v] as LrSnapshot; 114 res.version = v; 115 return res 116 }).sort( 117 (a,b) => a.version > b.version ? -1 : 1 118 ) 119 120 let selected = metadata[0]; 121 122 setTimeout(() => { 123 this.setState({ 124 metadata, 125 selected 126 }) 127 }) 128 } 129 } 130 131 const SiteStructure = styled.div` 132 display: flex; 133 ` 134 135 const Nav = styled.div` 136 padding: 18px; 137 background: ${props => props.theme.colors.bgDarker}; 138 box-shadow: ${props => props.theme.shadows.default}; 139 margin-right: 12px; 140 min-height: 100vh; 141 ` 142 143 const NavItem = styled.div<{ 144 selected: boolean 145 }>` 146 color: ${props => props.selected ? props.theme.colors.primary : "inherit"}; 147 cursor: pointer; 148 ` 149 150 type VersionsProps = { 151 versions: string[] 152 selected: string 153 onSelect: (string) => void 154 } 155 export class Versions extends React.Component<VersionsProps, {}> { 156 render() { 157 return ( 158 <Nav> 159 {this.props.versions.map(v => ( 160 <NavItem key={v} 161 selected={v == this.props.selected} 162 onClick={() => this.props.onSelect(v)} 163 >{v}</NavItem> 164 ))} 165 </Nav> 166 ) 167 } 168 } 169 170 const Container = styled.div` 171 padding: 24px; 172 ` 173 174 const InfoLine = styled.div` 175 padding: 0 0 12px 0; 176 177 & span { 178 margin-right: 48px; 179 display: inline-block; 180 } 181 ` 182 183 type ResourcesProps = { 184 snapshot: LrSnapshot 185 prev: LrSnapshot 186 } 187 export class Resources extends React.Component<ResourcesProps, {}> { 188 render() { 189 let snap = this.props.snapshot 190 window.SNAP = snap; // yeah yeah TODO: remove me 191 192 let resources = snap.Resources.sort((a,b) => (a.ID > b.ID) ? 1 : -1 ) 193 // resources = this.computeUpdatedResources(resources, this.props.prev.Resources) 194 195 let fieldsCnt = 0 196 resources.forEach(r => { 197 if (r.Body.Fields != null) { 198 fieldsCnt += r.Body.Fields.length 199 } 200 }) 201 202 let cards = resources.map((resource, idx) => { 203 return <Resource resource={resource} key={idx} /> 204 }) 205 206 return ( 207 <Container> 208 <InfoLine> 209 <span>Version: {snap.version}</span> 210 <span>Resources: {snap.Resources.length}</span> 211 <span>Fields: {fieldsCnt}</span> 212 </InfoLine> 213 <Cards> 214 {cards} 215 </Cards> 216 </Container> 217 ) 218 } 219 220 computeUpdatedResources(resources: LrResource[], prevResources: LrResource[]): LrResource[] { 221 let prev = {} 222 prevResources.map(x => prev[x.ID] = x) 223 224 let updatedResources = [] 225 resources.forEach(resource => { 226 let prevResource = prev[resource.ID] 227 if (prevResource == null) { 228 resource.updated = true 229 resource.Body.Fields.forEach(field => field.updated = true) 230 updatedResources.push(resource) 231 return 232 } 233 234 if (resource.Body.Fields == null) { 235 return 236 } 237 238 let hasUpdates = false 239 resource.Body.Fields.forEach((field) => { 240 let updated = prevResource.Body.Fields.find(f => f.ID == field.ID) == null; 241 if (updated) { 242 field.updated = true 243 hasUpdates = true 244 } 245 }) 246 247 if (hasUpdates) { 248 updatedResources.push(resource) 249 resource.updated = false 250 } 251 }) 252 253 return updatedResources 254 } 255 } 256 257 const Cards = styled.div` 258 display: flex; 259 flex-wrap: wrap; 260 `; 261 262 const Card = styled.div` 263 max-width: 400px; 264 min-width: 200px; 265 min-height: 100px; 266 font-size: 24px; 267 border: 2px solid #333; 268 border-radius: 7px; 269 margin: 0 24px 24px 0; 270 padding: 15px 24px 24px 24px; 271 background: ${props => props.theme.colors.bgDarker}; 272 273 &:hover { 274 box-shadow: ${props => props.theme.shadows.default}; 275 } 276 ` 277 const Name = styled.span<{ 278 updated?: boolean 279 }>` 280 color: ${props => (props.updated !== false) ? props.theme.colors.primary : "white"}; 281 ` 282 283 const Inits = styled.span` 284 font-size: 16px; 285 ` 286 287 const FieldName = styled.span<{ 288 updated?: boolean 289 }>` 290 color: ${props => (props.updated === true) ? props.theme.colors.primary : "inherit"}; 291 cursor: pointer; 292 293 &:hover { 294 color: ${props => props.theme.colors.secondary}; 295 } 296 ` 297 298 const FieldType = styled.span` 299 font-size: 16px; 300 color: #aaa; 301 `; 302 303 type ResourceProps = { 304 resource: LrResource 305 } 306 export class Resource extends React.Component<ResourceProps, {}> { 307 render() { 308 let { resource } = this.props 309 310 let inits = this.renderInit(resource.Body.Inits) 311 let listType = null; 312 if (resource.ListType != null) { 313 listType = "[]"+resource.ListType.Type.Type 314 } 315 316 let fields = (resource.Body.Fields || []).map((field) => this.renderField(field)) 317 318 return ( 319 <Card> 320 <div> 321 <Name updated={resource.updated}>{resource.ID}</Name> <Inits>{inits}</Inits> 322 </div> 323 {listType} 324 {fields} 325 </Card> 326 ) 327 } 328 329 renderInit(inits: LrInit[] | null): React.ReactElement | null { 330 if (inits == null || inits.length == 0) { 331 return ( 332 null 333 ) 334 } 335 336 if (inits.length != 1) { 337 console.log("too many inits, ignoring everything else:") 338 console.log(inits) 339 } 340 341 let init = inits[0]; 342 return ( 343 <span> 344 ( {init.Args.map((arg) => arg.ID + " " + renderLrType(arg.Type)).join(", ")} ) 345 </span> 346 ) 347 } 348 349 renderField(field: LrField): React.ReactElement { 350 return ( 351 <div key={field.ID}> 352 <FieldName updated={field.updated}>{field.ID}</FieldName> <FieldType>{renderLrType(field.Type)}</FieldType> 353 </div> 354 ) 355 } 356 } 357 358 function renderLrType(t: LrType): string { 359 if (t.SimpleType != null) return t.SimpleType.Type; 360 if (t.ListType != null) return "[]"+renderLrType(t.ListType.Type); 361 if (t.MapType != null) return "map["+t.MapType.Key.Type+"]"+renderLrType(t.MapType.Value); 362 return "?"; 363 }