Skip to content

Commit f5edc56

Browse files
authored
Merge pull request #383 from devtron-labs/feat/resource-browser-visualizations
feat: Resource Browser Visualizations
2 parents 67e909d + e0663a6 commit f5edc56

File tree

8 files changed

+136
-4
lines changed

8 files changed

+136
-4
lines changed

package-lock.json

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "0.6.0-patch-1-beta-7",
3+
"version": "0.6.0-patch-1-beta-10",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
@@ -40,6 +40,7 @@
4040
"@testing-library/react": "^12.1.4",
4141
"@tippyjs/react": "^4.2.0",
4242
"@typeform/embed-react": "2.20.0",
43+
"@types/dompurify": "^3.0.5",
4344
"@types/react": "17.0.39",
4445
"@types/react-dom": "17.0.13",
4546
"@types/react-router-dom": "^5.3.3",

src/Common/Constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export const URLS = {
6666
GLOBAL_CONFIG_SCOPED_VARIABLES: '/global-config/scoped-variables',
6767
GLOBAL_CONFIG_DEPLOYMENT_CHARTS_LIST: '/global-config/deployment-charts',
6868
NETWORK_STATUS_INTERFACE: '/network-status-interface',
69+
RESOURCE_BROWSER: '/resource-browser',
6970
}
7071

7172
export const ROUTES = {

src/Common/Helper.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
18+
import DOMPurify from 'dompurify'
1819
import { JSONPath, JSONPathOptions } from 'jsonpath-plus'
1920
import { compare as compareJSON, applyPatch } from 'fast-json-patch'
2021
import { components } from 'react-select'
@@ -642,7 +643,11 @@ export const powerSetOfSubstringsFromStart = (strings: string[], regex: RegExp)
642643
return _keys
643644
})
644645

645-
export const convertJSONPointerToJSONPath = (pointer: string) => pointer.replace(/\/([\*0-9]+)\//g, '[$1].').replace(/\//g, '.').replace(/\./, '$.')
646+
export const convertJSONPointerToJSONPath = (pointer: string) =>
647+
pointer
648+
.replace(/\/([\*0-9]+)\//g, '[$1].')
649+
.replace(/\//g, '.')
650+
.replace(/\./, '$.')
646651

647652
export const flatMapOfJSONPaths = (
648653
paths: string[],
@@ -953,3 +958,42 @@ export const throttle = <T extends (...args: unknown[]) => unknown>(
953958
}
954959
}
955960
}
961+
962+
// TODO: Might need to expose sandbox and referrer policy
963+
export const getSanitizedIframe = (iframeString: string) =>
964+
DOMPurify.sanitize(iframeString, {
965+
ADD_TAGS: ['iframe'],
966+
ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling'],
967+
})
968+
969+
/**
970+
* This method adds default attributes to iframe - title, loading ="lazy", width="100%", height="100%"
971+
*/
972+
export const getIframeWithDefaultAttributes = (iframeString: string, defaultName?: string): string => {
973+
const parentDiv = document.createElement('div')
974+
parentDiv.innerHTML = getSanitizedIframe(iframeString)
975+
976+
977+
const iframe = parentDiv.querySelector('iframe')
978+
if (iframe) {
979+
if (!iframe.hasAttribute('title') && !!defaultName) {
980+
iframe.setAttribute('title', defaultName)
981+
}
982+
983+
if (!iframe.hasAttribute('loading')) {
984+
iframe.setAttribute('loading', 'lazy')
985+
}
986+
987+
if (!iframe.hasAttribute('width')) {
988+
iframe.setAttribute('width', '100%')
989+
}
990+
991+
if (!iframe.hasAttribute('height')) {
992+
iframe.setAttribute('height', '100%')
993+
}
994+
995+
return parentDiv.innerHTML
996+
}
997+
998+
return iframeString
999+
}

src/Pages/ResourceBrowser/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616

1717
export * from './ResourceBrowser.Types'
1818
export * from './Helper'
19+
export * from './types'
1920
export * from './service'

src/Pages/ResourceBrowser/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export enum ClusterFiltersType {
2+
ALL_CLUSTERS = 'all',
3+
HEALTHY = 'healthy',
4+
UNHEALTHY = 'unhealthy',
5+
}
6+
7+
export enum ClusterStatusType {
8+
HEALTHY = 'healthy',
9+
UNHEALTHY = 'unhealthy',
10+
CONNECTION_FAILED = 'connection failed',
11+
}

src/Shared/types.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { Dayjs } from 'dayjs'
1718
import {
1819
OptionType,
1920
CommonNodeAttr,
@@ -118,6 +119,7 @@ export enum Nodes {
118119
Event = 'Event',
119120
Namespace = 'Namespace',
120121
Overview = 'Overview',
122+
MonitoringDashboard = 'MonitoringDashboard',
121123
}
122124

123125
// FIXME: This should be `typeof Nodes[keyof typeof Nodes]` instead since the key and values are not the same. Same to be removed from duplications in dashboard
@@ -680,6 +682,7 @@ export enum ConfigurationType {
680682
export interface BaseURLParams {
681683
appId: string
682684
envId: string
685+
clusterId: string
683686
}
684687

685688
export interface ConfigKeysWithLockType {
@@ -738,3 +741,31 @@ export interface CustomRoleAndMeta {
738741
possibleRolesMetaForCluster: MetaPossibleRoles
739742
possibleRolesMetaForJob: MetaPossibleRoles
740743
}
744+
745+
interface CommonTabArgsType {
746+
name: string
747+
kind?: string
748+
url: string
749+
isSelected: boolean
750+
title?: string
751+
isDeleted?: boolean
752+
position: number
753+
iconPath?: string
754+
dynamicTitle?: string
755+
showNameOnSelect?: boolean
756+
/**
757+
* @default false
758+
*/
759+
hideName?: boolean
760+
isAlive?: boolean
761+
lastSyncMoment?: Dayjs
762+
componentKey?: string
763+
}
764+
765+
export interface InitTabType extends CommonTabArgsType {
766+
idPrefix: string
767+
}
768+
769+
export interface DynamicTabType extends CommonTabArgsType {
770+
id: string
771+
}

src/Shared/validations.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { getSanitizedIframe } from '@Common/Helper'
1718
import { URLProtocolType } from './types'
1819

1920
export interface ValidationResponseType {
@@ -342,3 +343,29 @@ export const validateJSON = (json: string): ValidationResponseType => {
342343
}
343344
}
344345
}
346+
347+
export const validateIframe = (input: string): ValidationResponseType => {
348+
const sanitizedInput = getSanitizedIframe(input)
349+
const parentDiv = document.createElement('div')
350+
parentDiv.innerHTML = sanitizedInput
351+
352+
const iframe = parentDiv.querySelector('iframe')
353+
354+
// TODO: Can also check for accessability and security tags like sandbox, title, lazy, etc
355+
if (!iframe || parentDiv.children.length !== 1) {
356+
return { isValid: false, message: 'Input must contain a single iframe tag.' }
357+
}
358+
359+
const src = iframe.getAttribute('src')
360+
if (!src) {
361+
return { isValid: false, message: 'Iframe must have a valid src attribute.' }
362+
}
363+
364+
const urlValidationResponse = validateURL(src)
365+
366+
if (!urlValidationResponse.isValid) {
367+
return urlValidationResponse
368+
}
369+
370+
return { isValid: true }
371+
}

0 commit comments

Comments
 (0)