\n): string {\n return isTag(target) ? `styled.${target}` : `Styled(${getComponentName(target)})`;\n}\n","/**\n * Convenience function for joining strings to form className chains\n */\nexport default function joinStrings(a: ?String, b: ?String): ?String {\n return a && b ? `${a} ${b}` : a || b;\n}\n","// @flow\n// Thanks to ReactDOMFactories for this handy list!\n\nexport default [\n 'a',\n 'abbr',\n 'address',\n 'area',\n 'article',\n 'aside',\n 'audio',\n 'b',\n 'base',\n 'bdi',\n 'bdo',\n 'big',\n 'blockquote',\n 'body',\n 'br',\n 'button',\n 'canvas',\n 'caption',\n 'cite',\n 'code',\n 'col',\n 'colgroup',\n 'data',\n 'datalist',\n 'dd',\n 'del',\n 'details',\n 'dfn',\n 'dialog',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'embed',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'head',\n 'header',\n 'hgroup',\n 'hr',\n 'html',\n 'i',\n 'iframe',\n 'img',\n 'input',\n 'ins',\n 'kbd',\n 'keygen',\n 'label',\n 'legend',\n 'li',\n 'link',\n 'main',\n 'map',\n 'mark',\n 'marquee',\n 'menu',\n 'menuitem',\n 'meta',\n 'meter',\n 'nav',\n 'noscript',\n 'object',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'param',\n 'picture',\n 'pre',\n 'progress',\n 'q',\n 'rp',\n 'rt',\n 'ruby',\n 's',\n 'samp',\n 'script',\n 'section',\n 'select',\n 'small',\n 'source',\n 'span',\n 'strong',\n 'style',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'title',\n 'tr',\n 'track',\n 'u',\n 'ul',\n 'var',\n 'video',\n 'wbr',\n\n // SVG\n 'circle',\n 'clipPath',\n 'defs',\n 'ellipse',\n 'foreignObject',\n 'g',\n 'image',\n 'line',\n 'linearGradient',\n 'marker',\n 'mask',\n 'path',\n 'pattern',\n 'polygon',\n 'polyline',\n 'radialGradient',\n 'rect',\n 'stop',\n 'svg',\n 'text',\n 'textPath',\n 'tspan',\n];\n","// @flow\nimport constructWithOptions from './constructWithOptions';\nimport StyledComponent from '../models/StyledComponent';\nimport domElements from '../utils/domElements';\n\nimport type { Target } from '../types';\n\nconst styled = (tag: Target) => constructWithOptions(StyledComponent, tag);\n\n// Shorthands for all valid HTML Elements\ndomElements.forEach(domElement => {\n styled[domElement] = styled(domElement);\n});\n\nexport default styled;\n","// @flow\nimport { isValidElementType } from 'react-is';\nimport css from './css';\nimport throwStyledError from '../utils/error';\nimport { EMPTY_OBJECT } from '../utils/empties';\n\nimport type { Target } from '../types';\n\nexport default function constructWithOptions(\n componentConstructor: Function,\n tag: Target,\n options: Object = EMPTY_OBJECT\n) {\n if (!isValidElementType(tag)) {\n return throwStyledError(1, String(tag));\n }\n\n /* This is callable directly as a template function */\n // $FlowFixMe: Not typed to avoid destructuring arguments\n const templateFunction = (...args) => componentConstructor(tag, options, css(...args));\n\n /* If config methods are called, wrap up a new template function and merge options */\n templateFunction.withConfig = config =>\n constructWithOptions(componentConstructor, tag, { ...options, ...config });\n\n /* Modify/inject new props at runtime */\n templateFunction.attrs = attrs =>\n constructWithOptions(componentConstructor, tag, {\n ...options,\n attrs: Array.prototype.concat(options.attrs, attrs).filter(Boolean),\n });\n\n return templateFunction;\n}\n","// @flow\nimport StyleSheet from '../sheet';\nimport type { RuleSet, Stringifier } from '../types';\nimport flatten from '../utils/flatten';\nimport isStaticRules from '../utils/isStaticRules';\n\nexport default class GlobalStyle {\n componentId: string;\n\n isStatic: boolean;\n\n rules: RuleSet;\n\n constructor(rules: RuleSet, componentId: string) {\n this.rules = rules;\n this.componentId = componentId;\n this.isStatic = isStaticRules(rules);\n\n // pre-register the first instance to ensure global styles\n // load before component ones\n StyleSheet.registerId(this.componentId + 1);\n }\n\n createStyles(\n instance: number,\n executionContext: Object,\n styleSheet: StyleSheet,\n stylis: Stringifier\n ) {\n const flatCSS = flatten(this.rules, executionContext, styleSheet, stylis);\n const css = stylis(flatCSS.join(''), '');\n const id = this.componentId + instance;\n\n // NOTE: We use the id as a name as well, since these rules never change\n styleSheet.insertRules(id, id, css);\n }\n\n removeStyles(instance: number, styleSheet: StyleSheet) {\n styleSheet.clearRules(this.componentId + instance);\n }\n\n renderStyles(\n instance: number,\n executionContext: Object,\n styleSheet: StyleSheet,\n stylis: Stringifier\n ) {\n if (instance > 2) StyleSheet.registerId(this.componentId + instance);\n\n // NOTE: Remove old styles, then inject the new ones\n this.removeStyles(instance, styleSheet);\n this.createStyles(instance, executionContext, styleSheet, stylis);\n }\n}\n","// @flow\n/* eslint-disable no-underscore-dangle */\nimport React from 'react';\nimport { IS_BROWSER, SC_ATTR, SC_ATTR_VERSION, SC_VERSION } from '../constants';\nimport throwStyledError from '../utils/error';\nimport getNonce from '../utils/nonce';\nimport StyleSheet from '../sheet';\nimport StyleSheetManager from './StyleSheetManager';\n\ndeclare var __SERVER__: boolean;\n\nconst CLOSING_TAG_R = /^\\s*<\\/[a-z]/i;\n\nexport default class ServerStyleSheet {\n isStreaming: boolean;\n\n instance: StyleSheet;\n\n sealed: boolean;\n\n constructor() {\n this.instance = new StyleSheet({ isServer: true });\n this.sealed = false;\n }\n\n _emitSheetCSS = (): string => {\n const css = this.instance.toString();\n if (!css) return '';\n\n const nonce = getNonce();\n const attrs = [nonce && `nonce=\"${nonce}\"`, `${SC_ATTR}=\"true\"`, `${SC_ATTR_VERSION}=\"${SC_VERSION}\"`];\n const htmlAttr = attrs.filter(Boolean).join(' ');\n\n return ``;\n };\n\n collectStyles(children: any) {\n if (this.sealed) {\n return throwStyledError(2);\n }\n\n return {children};\n }\n\n getStyleTags = (): string => {\n if (this.sealed) {\n return throwStyledError(2);\n }\n\n return this._emitSheetCSS();\n };\n\n getStyleElement = () => {\n if (this.sealed) {\n return throwStyledError(2);\n }\n\n const props = {\n [SC_ATTR]: '',\n [SC_ATTR_VERSION]: SC_VERSION,\n dangerouslySetInnerHTML: {\n __html: this.instance.toString(),\n },\n };\n\n const nonce = getNonce();\n if (nonce) {\n (props: any).nonce = nonce;\n }\n\n // v4 returned an array for this fn, so we'll do the same for v5 for backward compat\n return [];\n };\n\n // eslint-disable-next-line consistent-return\n interleaveWithNodeStream(input: any) {\n if (!__SERVER__ || IS_BROWSER) {\n return throwStyledError(3);\n } else if (this.sealed) {\n return throwStyledError(2);\n }\n\n if (__SERVER__) {\n this.seal();\n\n // eslint-disable-next-line global-require\n const { Readable, Transform } = require('stream');\n\n const readableStream: Readable = input;\n const { instance: sheet, _emitSheetCSS } = this;\n\n const transformer = new Transform({\n transform: function appendStyleChunks(chunk, /* encoding */ _, callback) {\n // Get the chunk and retrieve the sheet's CSS as an HTML chunk,\n // then reset its rules so we get only new ones for the next chunk\n const renderedHtml = chunk.toString();\n const html = _emitSheetCSS();\n\n sheet.clearTag();\n\n // prepend style html to chunk, unless the start of the chunk is a\n // closing tag in which case append right after that\n if (CLOSING_TAG_R.test(renderedHtml)) {\n const endOfClosingTag = renderedHtml.indexOf('>') + 1;\n const before = renderedHtml.slice(0, endOfClosingTag);\n const after = renderedHtml.slice(endOfClosingTag);\n\n this.push(before + html + after);\n } else {\n this.push(html + renderedHtml);\n }\n\n callback();\n },\n });\n\n readableStream.on('error', err => {\n // forward the error to the transform stream\n transformer.emit('error', err);\n });\n\n return readableStream.pipe(transformer);\n }\n }\n\n seal = () => {\n this.sealed = true;\n };\n}\n","// @flow\n/* Import singletons */\nimport isStyledComponent from './utils/isStyledComponent';\nimport css from './constructors/css';\nimport createGlobalStyle from './constructors/createGlobalStyle';\nimport keyframes from './constructors/keyframes';\nimport ServerStyleSheet from './models/ServerStyleSheet';\nimport { SC_VERSION } from './constants';\n\nimport StyleSheetManager, {\n StyleSheetContext,\n StyleSheetConsumer,\n} from './models/StyleSheetManager';\n\n/* Import components */\nimport ThemeProvider, { ThemeContext, ThemeConsumer } from './models/ThemeProvider';\n\n/* Import Higher Order Components */\nimport withTheme from './hoc/withTheme';\n\n/* Import hooks */\nimport useTheme from './hooks/useTheme';\n\ndeclare var __SERVER__: boolean;\n\n/* Warning if you've imported this file on React Native */\nif (\n process.env.NODE_ENV !== 'production' &&\n typeof navigator !== 'undefined' &&\n navigator.product === 'ReactNative'\n) {\n // eslint-disable-next-line no-console\n console.warn(\n \"It looks like you've imported 'styled-components' on React Native.\\n\" +\n \"Perhaps you're looking to import 'styled-components/native'?\\n\" +\n 'Read more about this at https://www.styled-components.com/docs/basics#react-native'\n );\n}\n\n/* Warning if there are several instances of styled-components */\nif (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' && typeof window !== 'undefined') {\n window['__styled-components-init__'] = window['__styled-components-init__'] || 0;\n\n if (window['__styled-components-init__'] === 1) {\n // eslint-disable-next-line no-console\n console.warn(\n \"It looks like there are several instances of 'styled-components' initialized in this application. \" +\n 'This may cause dynamic styles to not render properly, errors during the rehydration process, ' +\n 'a missing theme prop, and makes your application bigger without good reason.\\n\\n' +\n 'See https://s-c.sh/2BAXzed for more info.'\n );\n }\n\n window['__styled-components-init__'] += 1;\n}\n\n/* Export everything */\nexport * from './secretInternals';\nexport {\n createGlobalStyle,\n css,\n isStyledComponent,\n keyframes,\n ServerStyleSheet,\n StyleSheetConsumer,\n StyleSheetContext,\n StyleSheetManager,\n ThemeConsumer,\n ThemeContext,\n ThemeProvider,\n useTheme,\n SC_VERSION as version,\n withTheme,\n};\n","import styled from 'styled-components'\n\nconst parseInlineStyle = (style: string) => {\n const template = document.createElement('template')\n template.setAttribute('style', style)\n return Object.entries(template.style)\n .filter(([key]) => !/^[0-9]+$/.test(key))\n .filter(([, value]) => Boolean(value))\n .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})\n}\n\nconst StyledButton = styled.a`\n display: flex;\n align-items: center;\n height: 44px;\n padding: 0 12px;\n background-color: #54b4cd;\n border-radius: 8px;\n font-size: 1rem;\n font-weight: 700;\n text-decoration: none;\n color: #ffffff;\n position: relative;\n\n span {\n margin-bottom: 1px;\n }\n\n &:hover {\n filter: brightness(110%);\n }\n`\n\nconst HubButton = ({ style }: { style?: string }) => {\n return (\n \n Upgrade to Traefik Hub\n \n )\n}\n\nexport default HubButton\n","import HubButton from 'components/HubButton'\n\nexport const App = ({ style }: { style?: string }) => {\n return \n}\n\nexport default App\n","import type { ComponentType } from 'react'\nimport React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport { StyleSheetManager } from 'styled-components'\n\ntype BaseProps = { [key: string]: string | undefined }\n\ntype Args = {\n name: string\n component: ComponentType
\n attributes?: string[]\n}\n\n/**\n * Register a custom element that wraps a React component.\n *\n * @param name - the name of the custom element\n * @param component - the React component\n */\nexport default function registerCustomElement
({\n name,\n component: Component,\n}: Args
) {\n const webComponentClass = class extends HTMLElement {\n private readonly styleHost: HTMLElement\n private readonly mountPoint: HTMLElement\n\n constructor() {\n super()\n\n this.styleHost = document.createElement('head')\n this.mountPoint = document.createElement('div')\n this.attachShadow({ mode: 'open' })\n }\n\n connectedCallback() {\n if (this.isConnected) {\n const attrs = Object.assign({}, ...Array.from(this.attributes, ({ name, value }) => ({ [name]: value })))\n\n this.shadowRoot?.appendChild(this.styleHost)\n this.shadowRoot?.appendChild(this.mountPoint)\n\n const mount = ReactDOM.createRoot(this.mountPoint)\n mount.render(\n \n \n ,\n )\n }\n }\n\n disconnectedCallback() {\n if (!this.isConnected) {\n this.shadowRoot?.removeChild(this.mountPoint)\n this.shadowRoot?.removeChild(this.styleHost)\n }\n }\n }\n\n customElements.define(name, webComponentClass)\n}\n","import App from 'App'\nimport registerCustomElement from 'utils/register-custom-element'\n\nregisterCustomElement({\n name: 'hub-button-app',\n component: App,\n})\n"],"names":["reactIs","require","REACT_STATICS","childContextTypes","contextType","contextTypes","defaultProps","displayName","getDefaultProps","getDerivedStateFromError","getDerivedStateFromProps","mixins","propTypes","type","KNOWN_STATICS","name","length","prototype","caller","callee","arguments","arity","MEMO_STATICS","compare","TYPE_STATICS","getStatics","component","isMemo","ForwardRef","render","Memo","defineProperty","Object","getOwnPropertyNames","getOwnPropertySymbols","getOwnPropertyDescriptor","getPrototypeOf","objectPrototype","module","exports","hoistNonReactStatics","targetComponent","sourceComponent","blacklist","inheritedComponent","keys","concat","targetStatics","sourceStatics","i","key","descriptor","e","b","Symbol","for","c","d","f","g","h","k","l","m","n","p","q","r","t","v","w","x","y","z","a","u","$$typeof","A","AsyncMode","ConcurrentMode","ContextConsumer","ContextProvider","Element","Fragment","Lazy","Portal","Profiler","StrictMode","Suspense","isAsyncMode","isConcurrentMode","isContextConsumer","isContextProvider","isElement","isForwardRef","isFragment","isLazy","isPortal","isProfiler","isStrictMode","isSuspense","isValidElementType","typeOf","aa","ca","encodeURIComponent","da","Set","ea","fa","ha","add","ia","window","document","createElement","ja","hasOwnProperty","ka","la","ma","this","acceptsBooleans","attributeName","attributeNamespace","mustUseProperty","propertyName","sanitizeURL","removeEmptyString","split","forEach","toLowerCase","ra","sa","toUpperCase","ta","slice","pa","isNaN","qa","call","test","oa","removeAttribute","setAttribute","setAttributeNS","replace","xlinkHref","ua","__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED","va","wa","ya","za","Aa","Ba","Ca","Da","Ea","Fa","Ga","Ha","Ia","Ja","iterator","Ka","La","assign","Ma","Error","stack","trim","match","Na","Oa","prepareStackTrace","set","Reflect","construct","includes","Pa","tag","Qa","_context","_payload","_init","Ra","Sa","Ta","nodeName","Va","_valueTracker","constructor","get","configurable","enumerable","getValue","setValue","stopTracking","Ua","Wa","checked","value","Xa","activeElement","body","Ya","defaultChecked","defaultValue","_wrapperState","initialChecked","Za","initialValue","controlled","ab","bb","cb","db","ownerDocument","eb","Array","isArray","fb","options","selected","defaultSelected","disabled","gb","dangerouslySetInnerHTML","children","hb","ib","jb","textContent","kb","lb","mb","nb","namespaceURI","innerHTML","valueOf","toString","firstChild","removeChild","appendChild","MSApp","execUnsafeLocalFunction","ob","lastChild","nodeType","nodeValue","pb","animationIterationCount","aspectRatio","borderImageOutset","borderImageSlice","borderImageWidth","boxFlex","boxFlexGroup","boxOrdinalGroup","columnCount","columns","flex","flexGrow","flexPositive","flexShrink","flexNegative","flexOrder","gridArea","gridRow","gridRowEnd","gridRowSpan","gridRowStart","gridColumn","gridColumnEnd","gridColumnSpan","gridColumnStart","fontWeight","lineClamp","lineHeight","opacity","order","orphans","tabSize","widows","zIndex","zoom","fillOpacity","floodOpacity","stopOpacity","strokeDasharray","strokeDashoffset","strokeMiterlimit","strokeOpacity","strokeWidth","qb","rb","sb","style","indexOf","setProperty","charAt","substring","tb","menuitem","area","base","br","col","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","ub","vb","is","wb","xb","target","srcElement","correspondingUseElement","parentNode","yb","zb","Ab","Bb","Cb","stateNode","Db","Eb","push","Fb","Gb","Hb","Ib","Jb","Kb","Lb","Mb","addEventListener","removeEventListener","Nb","apply","onError","Ob","Pb","Qb","Rb","Sb","Tb","Vb","alternate","return","flags","Wb","memoizedState","dehydrated","Xb","Zb","child","sibling","current","Yb","$b","ac","unstable_scheduleCallback","bc","unstable_cancelCallback","cc","unstable_shouldYield","dc","unstable_requestPaint","B","unstable_now","ec","unstable_getCurrentPriorityLevel","fc","unstable_ImmediatePriority","gc","unstable_UserBlockingPriority","hc","unstable_NormalPriority","ic","unstable_LowPriority","jc","unstable_IdlePriority","kc","lc","oc","Math","clz32","pc","qc","log","LN2","rc","sc","tc","uc","pendingLanes","suspendedLanes","pingedLanes","entangledLanes","entanglements","vc","xc","yc","zc","Ac","eventTimes","Cc","C","Dc","Ec","Fc","Gc","Hc","Ic","Jc","Kc","Lc","Mc","Nc","Oc","Map","Pc","Qc","Rc","Sc","delete","pointerId","Tc","nativeEvent","blockedOn","domEventName","eventSystemFlags","targetContainers","Vc","Wc","priority","isDehydrated","containerInfo","Xc","Yc","dispatchEvent","shift","Zc","$c","ad","bd","cd","ReactCurrentBatchConfig","dd","ed","transition","fd","gd","hd","id","Uc","stopPropagation","jd","kd","ld","md","nd","od","keyCode","charCode","pd","qd","rd","_reactName","_targetInst","currentTarget","isDefaultPrevented","defaultPrevented","returnValue","isPropagationStopped","preventDefault","cancelBubble","persist","isPersistent","wd","xd","yd","sd","eventPhase","bubbles","cancelable","timeStamp","Date","now","isTrusted","td","ud","view","detail","vd","Ad","screenX","screenY","clientX","clientY","pageX","pageY","ctrlKey","shiftKey","altKey","metaKey","getModifierState","zd","button","buttons","relatedTarget","fromElement","toElement","movementX","movementY","Bd","Dd","dataTransfer","Fd","Hd","animationName","elapsedTime","pseudoElement","Id","clipboardData","Jd","Ld","data","Md","Esc","Spacebar","Left","Up","Right","Down","Del","Win","Menu","Apps","Scroll","MozPrintableKey","Nd","Od","Alt","Control","Meta","Shift","Pd","Qd","String","fromCharCode","code","location","repeat","locale","which","Rd","Td","width","height","pressure","tangentialPressure","tiltX","tiltY","twist","pointerType","isPrimary","Vd","touches","targetTouches","changedTouches","Xd","Yd","deltaX","wheelDeltaX","deltaY","wheelDeltaY","wheelDelta","deltaZ","deltaMode","Zd","$d","ae","be","documentMode","ce","de","ee","fe","ge","he","ie","le","color","date","datetime","email","month","number","password","range","search","tel","text","time","url","week","me","ne","oe","event","listeners","pe","qe","re","se","te","ue","ve","we","xe","ye","ze","oninput","Ae","detachEvent","Be","Ce","attachEvent","De","Ee","Fe","He","Ie","Je","Ke","node","offset","nextSibling","Le","contains","compareDocumentPosition","Me","HTMLIFrameElement","contentWindow","href","Ne","contentEditable","Oe","focusedElem","selectionRange","documentElement","start","end","selectionStart","selectionEnd","min","defaultView","getSelection","extend","rangeCount","anchorNode","anchorOffset","focusNode","focusOffset","createRange","setStart","removeAllRanges","addRange","setEnd","element","left","scrollLeft","top","scrollTop","focus","Pe","Qe","Re","Se","Te","Ue","Ve","We","animationend","animationiteration","animationstart","transitionend","Xe","Ye","Ze","animation","$e","af","bf","cf","df","ef","ff","gf","hf","lf","mf","nf","Ub","instance","listener","D","of","has","pf","qf","rf","random","sf","bind","capture","passive","J","F","tf","uf","parentWindow","vf","wf","na","xa","$a","ba","je","char","ke","unshift","xf","yf","zf","Af","Bf","Cf","Df","Ef","__html","Ff","setTimeout","Gf","clearTimeout","Hf","Promise","Jf","queueMicrotask","resolve","then","catch","If","Kf","Lf","Mf","previousSibling","Nf","Of","Pf","Qf","Rf","Sf","Tf","Uf","E","G","Vf","H","Wf","Xf","Yf","__reactInternalMemoizedUnmaskedChildContext","__reactInternalMemoizedMaskedChildContext","Zf","$f","ag","bg","getChildContext","cg","__reactInternalMemoizedMergedChildContext","dg","eg","fg","gg","hg","jg","kg","lg","mg","ng","og","pg","qg","rg","sg","tg","ug","vg","wg","xg","yg","I","zg","Ag","Bg","elementType","deletions","Cg","pendingProps","overflow","treeContext","retryLane","Dg","mode","Eg","Fg","Gg","memoizedProps","Hg","Ig","Jg","Kg","Lg","Mg","Ng","Og","Pg","Qg","Rg","_currentValue","Sg","childLanes","Tg","dependencies","firstContext","lanes","Ug","Vg","context","memoizedValue","next","Wg","Xg","Yg","interleaved","Zg","$g","ah","updateQueue","baseState","firstBaseUpdate","lastBaseUpdate","shared","pending","effects","bh","ch","eventTime","lane","payload","callback","dh","K","eh","fh","gh","hh","ih","jh","Component","refs","kh","nh","isMounted","_reactInternals","enqueueSetState","L","lh","mh","enqueueReplaceState","enqueueForceUpdate","oh","shouldComponentUpdate","isPureReactComponent","ph","state","updater","qh","componentWillReceiveProps","UNSAFE_componentWillReceiveProps","rh","props","getSnapshotBeforeUpdate","UNSAFE_componentWillMount","componentWillMount","componentDidMount","sh","ref","_owner","_stringRef","th","join","uh","vh","index","wh","xh","yh","implementation","zh","Ah","done","Bh","Ch","Dh","Eh","Fh","Gh","Hh","Ih","tagName","Jh","Kh","Lh","M","Mh","revealOrder","Nh","Oh","_workInProgressVersionPrimary","Ph","ReactCurrentDispatcher","Qh","Rh","N","O","P","Sh","Th","Uh","Vh","Q","Wh","Xh","Yh","Zh","$h","ai","bi","ci","baseQueue","queue","di","ei","fi","lastRenderedReducer","action","hasEagerState","eagerState","lastRenderedState","dispatch","gi","hi","ii","ji","ki","getSnapshot","li","mi","R","ni","lastEffect","stores","oi","pi","qi","ri","create","destroy","deps","si","ti","ui","vi","wi","xi","yi","zi","Ai","Bi","Ci","Di","Ei","Fi","Gi","Hi","Ii","Ji","readContext","useCallback","useContext","useEffect","useImperativeHandle","useInsertionEffect","useLayoutEffect","useMemo","useReducer","useRef","useState","useDebugValue","useDeferredValue","useTransition","useMutableSource","useSyncExternalStore","useId","unstable_isNewReconciler","identifierPrefix","Ki","message","digest","Li","Mi","console","error","Ni","WeakMap","Oi","Pi","Qi","Ri","componentDidCatch","Si","componentStack","Ti","pingCache","Ui","Vi","Wi","Xi","ReactCurrentOwner","Yi","Zi","$i","aj","bj","cj","dj","ej","baseLanes","cachePool","transitions","fj","gj","hj","ij","jj","UNSAFE_componentWillUpdate","componentWillUpdate","componentDidUpdate","kj","lj","pendingContext","mj","Aj","Bj","Cj","Dj","nj","oj","pj","fallback","qj","rj","tj","dataset","dgst","uj","vj","_reactRetry","sj","subtreeFlags","wj","xj","isBackwards","rendering","renderingStartTime","last","tail","tailMode","yj","Ej","S","Fj","Gj","wasMultiple","multiple","suppressHydrationWarning","onClick","onclick","size","createElementNS","autoFocus","createTextNode","T","Hj","Ij","Jj","Kj","U","Lj","WeakSet","V","Mj","W","Nj","Oj","Qj","Rj","Sj","Tj","Uj","Vj","Wj","insertBefore","_reactRootContainer","Xj","X","Yj","Zj","ak","onCommitFiberUnmount","componentWillUnmount","bk","ck","dk","ek","fk","isHidden","gk","hk","display","ik","jk","kk","lk","__reactInternalSnapshotBeforeUpdate","src","Wk","mk","ceil","nk","ok","pk","Y","Z","qk","rk","sk","tk","uk","Infinity","vk","wk","xk","yk","zk","Ak","Bk","Ck","Dk","Ek","callbackNode","expirationTimes","expiredLanes","wc","callbackPriority","ig","Fk","Gk","Hk","Ik","Jk","Kk","Lk","Mk","Nk","Ok","Pk","finishedWork","finishedLanes","Qk","timeoutHandle","Rk","Sk","Tk","Uk","Vk","mutableReadLanes","Bc","Pj","onCommitFiberRoot","mc","onRecoverableError","Xk","onPostCommitFiberRoot","Yk","Zk","al","isReactComponent","pendingChildren","bl","mutableSourceEagerHydrationData","cl","cache","pendingSuspenseBoundaries","el","fl","gl","hl","il","jl","zj","$k","ll","reportError","ml","_internalRoot","nl","ol","pl","ql","sl","rl","unmount","unstable_scheduleHydration","splice","querySelectorAll","JSON","stringify","form","tl","usingClientEntryPoint","Events","ul","findFiberByHostInstance","bundleType","version","rendererPackageName","vl","rendererConfig","overrideHookState","overrideHookStateDeletePath","overrideHookStateRenamePath","overrideProps","overridePropsDeletePath","overridePropsRenamePath","setErrorHandler","setSuspenseHandler","scheduleUpdate","currentDispatcherRef","findHostInstanceByFiber","findHostInstancesForRefresh","scheduleRefresh","scheduleRoot","setRefreshHandler","getCurrentFiber","reconcilerVersion","__REACT_DEVTOOLS_GLOBAL_HOOK__","wl","isDisabled","supportsFiber","inject","createPortal","dl","createRoot","unstable_strictMode","findDOMNode","flushSync","hydrate","hydrateRoot","hydratedSources","_getVersion","_source","unmountComponentAtNode","unstable_batchedUpdates","unstable_renderSubtreeIntoContainer","checkDCE","err","getModuleId","__self","__source","jsx","setState","forceUpdate","escape","_status","_result","default","Children","map","count","toArray","only","PureComponent","cloneElement","createContext","_currentValue2","_threadCount","Provider","Consumer","_defaultValue","_globalName","createFactory","createRef","forwardRef","isValidElement","lazy","memo","startTransition","unstable_act","pop","sortIndex","performance","setImmediate","startTime","expirationTime","priorityLevel","navigator","scheduling","isInputPending","MessageChannel","port2","port1","onmessage","postMessage","unstable_Profiling","unstable_continueExecution","unstable_forceFrameRate","floor","unstable_getFirstCallbackNode","unstable_next","unstable_pauseExecution","unstable_runWithPriority","delay","unstable_wrapCallback","objA","objB","compareContext","ret","keysA","keysB","bHasOwnProperty","idx","valueA","valueB","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","getter","__esModule","definition","o","obj","prop","nc","charCodeAt","prefix","use","msGridRow","msGridRowSpan","msGridColumn","msGridColumnSpan","WebkitLineClamp","memoize","fn","arg","reactPropsRegex","isPropValid","freeze","_","styledComponentId","process","REACT_APP_SC_ATTR","SC_ATTR","Boolean","SC_DISABLE_SPEEDY","REACT_APP_SC_DISABLE_SPEEDY","j","groupSizes","Uint32Array","indexOfGroup","insertRules","s","insertRule","clearGroup","deleteRule","getGroup","getRule","RegExp","registerName","parseInt","getTag","__webpack_nonce__","head","childNodes","hasAttribute","sheet","styleSheets","ownerNode","cssRules","cssText","$","nodes","rules","isServer","useCSSOMInjection","gs","names","server","getAttribute","registerId","reconstructWithOptions","allocateGSInstance","hasNameForId","clearNames","clear","clearRules","clearTag","abs","staticRulesId","isStatic","componentId","baseHash","baseStyle","generateAndInjectStyles","hash","_e","plugins","lastIndexOf","reduce","stylisPlugins","disableCSSOMInjection","disableVendorPrefixes","getName","isCss","startsWith","theme","attrs","parentComponentId","filter","shouldForwardProp","componentStyle","foldedComponentIds","$as","as","className","withComponent","_foldedDefaultProps","withConfig","createStyles","removeStyles","renderStyles","_emitSheetCSS","getStyleTags","sealed","getStyleElement","nonce","seal","collectStyles","interleaveWithNodeStream","parseInlineStyle","template","entries","_ref","_ref2","acc","_ref3","StyledButton","styled","_ref4","_jsx","HubButton","webComponentClass","HTMLElement","super","styleHost","mountPoint","attachShadow","connectedCallback","isConnected","_this$shadowRoot","_this$shadowRoot2","from","attributes","shadowRoot","ReactDOM","StyleSheetManager","disconnectedCallback","_this$shadowRoot3","_this$shadowRoot4","customElements","define","registerCustomElement","App"],"sourceRoot":""}
\ No newline at end of file
diff --git a/webui/src/hooks/use-hub-upgrade-button.spec.tsx b/webui/src/hooks/use-hub-upgrade-button.spec.tsx
new file mode 100644
index 000000000..f0f914675
--- /dev/null
+++ b/webui/src/hooks/use-hub-upgrade-button.spec.tsx
@@ -0,0 +1,131 @@
+import { renderHook, waitFor } from '@testing-library/react'
+import { ReactNode } from 'react'
+
+import useHubUpgradeButton from './use-hub-upgrade-button'
+
+import { VersionContext } from 'contexts/version'
+import verifySignature from 'utils/workers/scriptVerification'
+
+vi.mock('utils/workers/scriptVerification')
+
+const mockVerifySignature = vi.mocked(verifySignature)
+
+const createWrapper = (showHubButton: boolean) => {
+ return ({ children }: { children: ReactNode }) => (
+ {children}
+ )
+}
+
+describe('useHubUpgradeButton Hook', () => {
+ let originalCreateObjectURL: typeof URL.createObjectURL
+ let originalRevokeObjectURL: typeof URL.revokeObjectURL
+ const mockBlobUrl = 'blob:http://localhost:3000/mock-blob-url'
+
+ beforeEach(() => {
+ originalCreateObjectURL = URL.createObjectURL
+ originalRevokeObjectURL = URL.revokeObjectURL
+ URL.createObjectURL = vi.fn(() => mockBlobUrl)
+ URL.revokeObjectURL = vi.fn()
+ })
+
+ afterEach(() => {
+ URL.createObjectURL = originalCreateObjectURL
+ URL.revokeObjectURL = originalRevokeObjectURL
+ vi.clearAllMocks()
+ })
+
+ it('should not verify script when showHubButton is false', async () => {
+ renderHook(() => useHubUpgradeButton(), {
+ wrapper: createWrapper(false),
+ })
+
+ await waitFor(() => {
+ expect(mockVerifySignature).not.toHaveBeenCalled()
+ })
+ })
+
+ it('should verify script and create blob URL when showHubButton is true and verification succeeds', async () => {
+ const mockScriptContent = new ArrayBuffer(8)
+ mockVerifySignature.mockResolvedValue({
+ verified: true,
+ scriptContent: mockScriptContent,
+ })
+
+ const { result } = renderHook(() => useHubUpgradeButton(), {
+ wrapper: createWrapper(true),
+ })
+
+ await waitFor(() => {
+ expect(mockVerifySignature).toHaveBeenCalledWith(
+ 'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js',
+ 'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js.sig',
+ 'MCowBQYDK2VwAyEAY0OZFFE5kSuqYK6/UprTL5RmvQ+8dpPTGMCw1MiO/Gs=',
+ )
+ })
+
+ await waitFor(() => {
+ expect(result.current.signatureVerified).toBe(true)
+ })
+
+ expect(result.current.scriptBlobUrl).toBe(mockBlobUrl)
+ expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob))
+ })
+
+ it('should set signatureVerified to false when verification fails', async () => {
+ mockVerifySignature.mockResolvedValue({
+ verified: false,
+ })
+
+ const { result } = renderHook(() => useHubUpgradeButton(), {
+ wrapper: createWrapper(true),
+ })
+
+ await waitFor(() => {
+ expect(mockVerifySignature).toHaveBeenCalled()
+ })
+
+ await waitFor(() => {
+ expect(result.current.signatureVerified).toBe(false)
+ })
+
+ expect(result.current.scriptBlobUrl).toBeNull()
+ expect(URL.createObjectURL).not.toHaveBeenCalled()
+ })
+
+ it('should handle verification errors gracefully', async () => {
+ mockVerifySignature.mockRejectedValue(new Error('Verification failed'))
+
+ const { result } = renderHook(() => useHubUpgradeButton(), {
+ wrapper: createWrapper(true),
+ })
+
+ await waitFor(() => {
+ expect(mockVerifySignature).toHaveBeenCalled()
+ })
+
+ await waitFor(() => {
+ expect(result.current.signatureVerified).toBe(false)
+ })
+
+ expect(result.current.scriptBlobUrl).toBeNull()
+ })
+
+ it('should create blob with correct MIME type', async () => {
+ const mockScriptContent = new ArrayBuffer(8)
+ mockVerifySignature.mockResolvedValue({
+ verified: true,
+ scriptContent: mockScriptContent,
+ })
+
+ renderHook(() => useHubUpgradeButton(), {
+ wrapper: createWrapper(true),
+ })
+
+ await waitFor(() => {
+ expect(URL.createObjectURL).toHaveBeenCalled()
+ })
+ const blobCall = vi.mocked(URL.createObjectURL).mock.calls[0][0] as Blob
+ expect(blobCall).toBeInstanceOf(Blob)
+ expect(blobCall.type).toBe('application/javascript')
+ })
+})
diff --git a/webui/src/hooks/use-hub-upgrade-button.tsx b/webui/src/hooks/use-hub-upgrade-button.tsx
new file mode 100644
index 000000000..197572899
--- /dev/null
+++ b/webui/src/hooks/use-hub-upgrade-button.tsx
@@ -0,0 +1,61 @@
+import { useContext, useEffect, useState } from 'react'
+
+import { VersionContext } from 'contexts/version'
+import verifySignature from 'utils/workers/scriptVerification'
+
+const HUB_BUTTON_URL = 'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js'
+const PUBLIC_KEY = 'MCowBQYDK2VwAyEAY0OZFFE5kSuqYK6/UprTL5RmvQ+8dpPTGMCw1MiO/Gs='
+
+const useHubUpgradeButton = () => {
+ const [signatureVerified, setSignatureVerified] = useState(false)
+ const [scriptBlobUrl, setScriptBlobUrl] = useState(null)
+ const [isCustomElementDefined, setIsCustomElementDefined] = useState(false)
+
+ const { showHubButton } = useContext(VersionContext)
+
+ useEffect(() => {
+ if (showHubButton) {
+ if (customElements.get('hub-button-app')) {
+ setSignatureVerified(true)
+ setIsCustomElementDefined(true)
+ return
+ }
+
+ const verifyAndLoadScript = async () => {
+ try {
+ const { verified, scriptContent: content } = await verifySignature(
+ HUB_BUTTON_URL,
+ `${HUB_BUTTON_URL}.sig`,
+ PUBLIC_KEY,
+ )
+ if (!verified || !content) {
+ setSignatureVerified(false)
+ } else {
+ const blob = new Blob([content], { type: 'application/javascript' })
+ const blobUrl = URL.createObjectURL(blob)
+
+ setScriptBlobUrl(blobUrl)
+ setSignatureVerified(true)
+ }
+ } catch {
+ setSignatureVerified(false)
+ }
+ }
+
+ verifyAndLoadScript()
+
+ return () => {
+ setScriptBlobUrl((currentUrl) => {
+ if (currentUrl) {
+ URL.revokeObjectURL(currentUrl)
+ }
+ return null
+ })
+ }
+ }
+ }, [showHubButton])
+
+ return { signatureVerified, scriptBlobUrl, isCustomElementDefined }
+}
+
+export default useHubUpgradeButton
diff --git a/webui/src/layout/EmptyPlaceholder.tsx b/webui/src/layout/EmptyPlaceholder.tsx
index e81ace9df..e62fcb112 100644
--- a/webui/src/layout/EmptyPlaceholder.tsx
+++ b/webui/src/layout/EmptyPlaceholder.tsx
@@ -1,9 +1,20 @@
-import { Flex, Text } from '@traefiklabs/faency'
+import { AriaTd, Flex, Text } from '@traefiklabs/faency'
import { FiAlertTriangle } from 'react-icons/fi'
-export const EmptyPlaceholder = ({ message = 'No data available' }: { message?: string }) => (
+type EmptyPlaceholderProps = {
+ message?: string
+}
+export const EmptyPlaceholder = ({ message = 'No data available' }: EmptyPlaceholderProps) => (
{message}
)
+
+export const EmptyPlaceholderTd = (props: EmptyPlaceholderProps) => {
+ return (
+
+
+
+ )
+}
diff --git a/webui/src/pages/http/HttpMiddlewares.tsx b/webui/src/pages/http/HttpMiddlewares.tsx
index e3274009e..4ec86d841 100644
--- a/webui/src/pages/http/HttpMiddlewares.tsx
+++ b/webui/src/pages/http/HttpMiddlewares.tsx
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
import { parseMiddlewareType } from 'libs/parsers'
export const makeRowRender = (): RenderRowType => {
@@ -79,9 +79,7 @@ export const HttpMiddlewaresRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/http/HttpRouters.tsx b/webui/src/pages/http/HttpRouters.tsx
index 06c2c255f..7896ca38a 100644
--- a/webui/src/pages/http/HttpRouters.tsx
+++ b/webui/src/pages/http/HttpRouters.tsx
@@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (protocol = 'http'): RenderRowType => {
const HttpRoutersRenderRow = (row) => (
@@ -100,9 +100,7 @@ export const HttpRoutersRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/http/HttpServices.tsx b/webui/src/pages/http/HttpServices.tsx
index 8c41badcd..e49bc82b6 100644
--- a/webui/src/pages/http/HttpServices.tsx
+++ b/webui/src/pages/http/HttpServices.tsx
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const HttpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const HttpServicesRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/hub-demo/HubDashboard.spec.tsx b/webui/src/pages/hub-demo/HubDashboard.spec.tsx
index 7f338ab69..f5b479586 100644
--- a/webui/src/pages/hub-demo/HubDashboard.spec.tsx
+++ b/webui/src/pages/hub-demo/HubDashboard.spec.tsx
@@ -1,12 +1,13 @@
import { waitFor } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
+import { PUBLIC_KEY } from './constants'
import HubDashboard, { resetCache } from './HubDashboard'
-import verifySignature from './workers/scriptVerification'
import { renderWithProviders } from 'utils/test'
+import verifySignature from 'utils/workers/scriptVerification'
-vi.mock('./workers/scriptVerification', () => ({
+vi.mock('utils/workers/scriptVerification', () => ({
default: vi.fn(),
}))
@@ -34,7 +35,6 @@ describe('HubDashboard demo', () => {
beforeEach(() => {
vi.clearAllMocks()
- // Mock URL.createObjectURL
mockCreateObjectURL = vi.fn(() => 'blob:mock-url')
globalThis.URL.createObjectURL = mockCreateObjectURL
})
@@ -45,7 +45,6 @@ describe('HubDashboard demo', () => {
describe('without cache', () => {
beforeEach(() => {
- // Reset cache before each test suites
resetCache()
})
@@ -130,6 +129,7 @@ describe('HubDashboard demo', () => {
expect(mockVerifyScriptSignature).toHaveBeenCalledWith(
'https://assets.traefik.io/hub-ui-demo.js',
'https://assets.traefik.io/hub-ui-demo.js.sig',
+ PUBLIC_KEY,
)
})
})
diff --git a/webui/src/pages/hub-demo/HubDashboard.tsx b/webui/src/pages/hub-demo/HubDashboard.tsx
index 33cdf8744..8ef777bb8 100644
--- a/webui/src/pages/hub-demo/HubDashboard.tsx
+++ b/webui/src/pages/hub-demo/HubDashboard.tsx
@@ -3,7 +3,9 @@ import { useMemo, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
-import verifySignature from './workers/scriptVerification'
+import verifySignature from '../../utils/workers/scriptVerification'
+
+import { PUBLIC_KEY } from './constants'
import { SpinnerLoader } from 'components/SpinnerLoader'
import { useIsDarkMode } from 'hooks/use-theme'
@@ -42,7 +44,7 @@ const HubDashboard = ({ path }: { path: string }) => {
setVerificationInProgress(true)
try {
- const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`)
+ const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`, PUBLIC_KEY)
if (!verified || !content) {
setScriptError(true)
diff --git a/webui/src/pages/hub-demo/constants.ts b/webui/src/pages/hub-demo/constants.ts
new file mode 100644
index 000000000..7cd4ec038
--- /dev/null
+++ b/webui/src/pages/hub-demo/constants.ts
@@ -0,0 +1 @@
+export const PUBLIC_KEY = 'MCowBQYDK2VwAyEAWMBZ0pMBaL/s8gNXxpAPCIQ8bxjnuz6bQFwGYvjXDfg='
diff --git a/webui/src/pages/hub-demo/use-hub-demo.spec.tsx b/webui/src/pages/hub-demo/use-hub-demo.spec.tsx
index dba13cc97..6d77c1d39 100644
--- a/webui/src/pages/hub-demo/use-hub-demo.spec.tsx
+++ b/webui/src/pages/hub-demo/use-hub-demo.spec.tsx
@@ -3,9 +3,10 @@ import { ReactNode } from 'react'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { useHubDemo } from './use-hub-demo'
-import verifySignature from './workers/scriptVerification'
-vi.mock('./workers/scriptVerification', () => ({
+import verifySignature from 'utils/workers/scriptVerification'
+
+vi.mock('utils/workers/scriptVerification', () => ({
default: vi.fn(),
}))
diff --git a/webui/src/pages/hub-demo/use-hub-demo.tsx b/webui/src/pages/hub-demo/use-hub-demo.tsx
index 4eee4f455..b23109c17 100644
--- a/webui/src/pages/hub-demo/use-hub-demo.tsx
+++ b/webui/src/pages/hub-demo/use-hub-demo.tsx
@@ -1,9 +1,11 @@
import { ReactNode, useEffect, useMemo, useState } from 'react'
import { RouteObject } from 'react-router-dom'
+import { PUBLIC_KEY } from './constants'
+
import HubDashboard from 'pages/hub-demo/HubDashboard'
import { ApiIcon, DashboardIcon, GatewayIcon, PortalIcon } from 'pages/hub-demo/icons'
-import verifySignature from 'pages/hub-demo/workers/scriptVerification'
+import verifySignature from 'utils/workers/scriptVerification'
const ROUTES_MANIFEST_URL = 'https://traefik.github.io/hub-ui-demo-app/config/routes.json'
@@ -20,7 +22,11 @@ const useHubDemoRoutesManifest = (): HubDemo.Manifest | null => {
useEffect(() => {
const fetchManifest = async () => {
try {
- const { verified, scriptContent } = await verifySignature(ROUTES_MANIFEST_URL, `${ROUTES_MANIFEST_URL}.sig`)
+ const { verified, scriptContent } = await verifySignature(
+ ROUTES_MANIFEST_URL,
+ `${ROUTES_MANIFEST_URL}.sig`,
+ PUBLIC_KEY,
+ )
if (!verified || !scriptContent) {
setManifest(null)
diff --git a/webui/src/pages/tcp/TcpMiddlewares.tsx b/webui/src/pages/tcp/TcpMiddlewares.tsx
index b0189a2e5..25bca597b 100644
--- a/webui/src/pages/tcp/TcpMiddlewares.tsx
+++ b/webui/src/pages/tcp/TcpMiddlewares.tsx
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
import { parseMiddlewareType } from 'libs/parsers'
export const makeRowRender = (): RenderRowType => {
@@ -79,9 +79,7 @@ export const TcpMiddlewaresRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/tcp/TcpRouters.tsx b/webui/src/pages/tcp/TcpRouters.tsx
index f3cd3d497..8a8f638ac 100644
--- a/webui/src/pages/tcp/TcpRouters.tsx
+++ b/webui/src/pages/tcp/TcpRouters.tsx
@@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const TcpRoutersRenderRow = (row) => (
@@ -96,9 +96,7 @@ export const TcpRoutersRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/tcp/TcpServices.tsx b/webui/src/pages/tcp/TcpServices.tsx
index 77480fd4e..13df8792b 100644
--- a/webui/src/pages/tcp/TcpServices.tsx
+++ b/webui/src/pages/tcp/TcpServices.tsx
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const TcpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const TcpServicesRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/udp/UdpRouters.tsx b/webui/src/pages/udp/UdpRouters.tsx
index ce6348b75..b468630ce 100644
--- a/webui/src/pages/udp/UdpRouters.tsx
+++ b/webui/src/pages/udp/UdpRouters.tsx
@@ -15,7 +15,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const UdpRoutersRenderRow = (row) => (
@@ -81,9 +81,7 @@ export const UdpRoutersRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/udp/UdpServices.tsx b/webui/src/pages/udp/UdpServices.tsx
index 5d6b47c66..76abc3d02 100644
--- a/webui/src/pages/udp/UdpServices.tsx
+++ b/webui/src/pages/udp/UdpServices.tsx
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
-import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const UdpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const UdpServicesRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
diff --git a/webui/src/pages/hub-demo/workers/scriptVerification.integration.spec.ts b/webui/src/utils/workers/scriptVerification.integration.spec.ts
similarity index 100%
rename from webui/src/pages/hub-demo/workers/scriptVerification.integration.spec.ts
rename to webui/src/utils/workers/scriptVerification.integration.spec.ts
diff --git a/webui/src/pages/hub-demo/workers/scriptVerification.spec.ts b/webui/src/utils/workers/scriptVerification.spec.ts
similarity index 82%
rename from webui/src/pages/hub-demo/workers/scriptVerification.spec.ts
rename to webui/src/utils/workers/scriptVerification.spec.ts
index 1f438aa71..0d6c0108e 100644
--- a/webui/src/pages/hub-demo/workers/scriptVerification.spec.ts
+++ b/webui/src/utils/workers/scriptVerification.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import verifySignature from './scriptVerification'
+const SCRIPT_PATH = 'https://example.com/script.js'
+const MOCK_PUBLIC_KEY = 'MCowBQYDK2VwAyEAWH71OHphISjNK3mizCR/BawiDxc6IXT1vFHpBcxSIA0='
class MockWorker {
onmessage: ((event: MessageEvent) => void) | null = null
onerror: ((error: ErrorEvent) => void) | null = null
@@ -46,17 +48,14 @@ describe('verifySignature', () => {
})
it('should return true when verification succeeds', async () => {
- const scriptPath = 'https://example.com/script.js'
- const signaturePath = 'https://example.com/script.js.sig'
-
- const promise = verifySignature(scriptPath, signaturePath)
+ const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
await new Promise((resolve) => setTimeout(resolve, 0))
expect(mockWorkerInstance.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
- scriptUrl: scriptPath,
- signatureUrl: signaturePath,
+ scriptUrl: SCRIPT_PATH,
+ signatureUrl: `${SCRIPT_PATH}.sig`,
requestId: expect.any(String),
}),
)
@@ -76,12 +75,9 @@ describe('verifySignature', () => {
})
it('should return false when verification fails', async () => {
- const scriptPath = 'https://example.com/script.js'
- const signaturePath = 'https://example.com/script.js.sig'
-
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
- const promise = verifySignature(scriptPath, signaturePath)
+ const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
await new Promise((resolve) => setTimeout(resolve, 0))
@@ -101,16 +97,12 @@ describe('verifySignature', () => {
})
it('should return false when worker throws an error', async () => {
- const scriptPath = 'https://example.com/script.js'
- const signaturePath = 'https://example.com/script.js.sig'
-
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
- const promise = verifySignature(scriptPath, signaturePath)
+ const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
await new Promise((resolve) => setTimeout(resolve, 0))
- // Simulate worker onerror event
const error = new Error('Worker crashed')
mockWorkerInstance.simulateError(error)
diff --git a/webui/src/pages/hub-demo/workers/scriptVerification.ts b/webui/src/utils/workers/scriptVerification.ts
similarity index 92%
rename from webui/src/pages/hub-demo/workers/scriptVerification.ts
rename to webui/src/utils/workers/scriptVerification.ts
index e03a438da..fb3ecedd5 100644
--- a/webui/src/pages/hub-demo/workers/scriptVerification.ts
+++ b/webui/src/utils/workers/scriptVerification.ts
@@ -3,12 +3,10 @@ export interface VerificationResult {
scriptContent?: ArrayBuffer
}
-const PUBLIC_KEY = 'MCowBQYDK2VwAyEAWMBZ0pMBaL/s8gNXxpAPCIQ8bxjnuz6bQFwGYvjXDfg='
-
async function verifySignature(
contentPath: string,
signaturePath: string,
- publicKey: string = PUBLIC_KEY,
+ publicKey: string,
): Promise {
return new Promise((resolve) => {
const requestId = Math.random().toString(36).substring(2)
diff --git a/webui/src/pages/hub-demo/workers/scriptVerificationWorker.ts b/webui/src/utils/workers/scriptVerificationWorker.ts
similarity index 100%
rename from webui/src/pages/hub-demo/workers/scriptVerificationWorker.ts
rename to webui/src/utils/workers/scriptVerificationWorker.ts
diff --git a/webui/yarn.lock b/webui/yarn.lock
index 480629146..efb7038fd 100644
--- a/webui/yarn.lock
+++ b/webui/yarn.lock
@@ -2871,24 +2871,24 @@ __metadata:
languageName: node
linkType: hard
-"@microsoft/api-extractor-model@npm:7.31.3":
- version: 7.31.3
- resolution: "@microsoft/api-extractor-model@npm:7.31.3"
+"@microsoft/api-extractor-model@npm:7.32.0":
+ version: 7.32.0
+ resolution: "@microsoft/api-extractor-model@npm:7.32.0"
dependencies:
- "@microsoft/tsdoc": "npm:~0.15.1"
- "@microsoft/tsdoc-config": "npm:~0.17.1"
+ "@microsoft/tsdoc": "npm:~0.16.0"
+ "@microsoft/tsdoc-config": "npm:~0.18.0"
"@rushstack/node-core-library": "npm:5.18.0"
- checksum: 10c0/4e4a798c5d92b72fa664932019563f085153cf33f7745f8ea452901348a0021f19c7c3db55e5555b779a78df52d93ec10960349b5bc1ee53bf555e63c0fe1197
+ checksum: 10c0/e6d9c54a457c66dec53765522f411c8ca5d2bb8c51559b9f952fdd7bb88b10efe035a8dbdf8b672644c136d75a65f3446b3b18938fa50c0a766f83111cbda5eb
languageName: node
linkType: hard
"@microsoft/api-extractor@npm:^7.50.1":
- version: 7.54.0
- resolution: "@microsoft/api-extractor@npm:7.54.0"
+ version: 7.55.0
+ resolution: "@microsoft/api-extractor@npm:7.55.0"
dependencies:
- "@microsoft/api-extractor-model": "npm:7.31.3"
- "@microsoft/tsdoc": "npm:~0.15.1"
- "@microsoft/tsdoc-config": "npm:~0.17.1"
+ "@microsoft/api-extractor-model": "npm:7.32.0"
+ "@microsoft/tsdoc": "npm:~0.16.0"
+ "@microsoft/tsdoc-config": "npm:~0.18.0"
"@rushstack/node-core-library": "npm:5.18.0"
"@rushstack/rig-package": "npm:0.6.0"
"@rushstack/terminal": "npm:0.19.3"
@@ -2902,26 +2902,26 @@ __metadata:
typescript: "npm:5.8.2"
bin:
api-extractor: bin/api-extractor
- checksum: 10c0/e4708ac5edc3bb32988b632cc75e6f5e5b3afe7772c7229974db91f731d6b8d3c786c406d5437bfce893b78c6f23c64b084db52952a7d35294e2525171a465ee
+ checksum: 10c0/3211981b7aaf6ca7a36fe33dc9cab5014dc753c0c75d09ace46bef07db947a433ca9daecc843ea13b29fe7527ea3d357c7bd5051fedf10ae3b3db31d2a5de71f
languageName: node
linkType: hard
-"@microsoft/tsdoc-config@npm:~0.17.1":
- version: 0.17.1
- resolution: "@microsoft/tsdoc-config@npm:0.17.1"
+"@microsoft/tsdoc-config@npm:~0.18.0":
+ version: 0.18.0
+ resolution: "@microsoft/tsdoc-config@npm:0.18.0"
dependencies:
- "@microsoft/tsdoc": "npm:0.15.1"
+ "@microsoft/tsdoc": "npm:0.16.0"
ajv: "npm:~8.12.0"
jju: "npm:~1.4.0"
resolve: "npm:~1.22.2"
- checksum: 10c0/a686355796f492f27af17e2a17d615221309caf4d9f9047a5a8f17f8625c467c4c81e2a7923ddafd71b892631d5e5013c4b8cc49c5867d3cc1d260fd90c1413d
+ checksum: 10c0/6e2c3bfde3e5fa4c0360127c86fe016dcf1b09d0091d767c06ce916284d3f6aeea3617a33b855c5bb2615ab0f2840eeebd4c7f4a1f879f951828d213bf306cfd
languageName: node
linkType: hard
-"@microsoft/tsdoc@npm:0.15.1, @microsoft/tsdoc@npm:~0.15.1":
- version: 0.15.1
- resolution: "@microsoft/tsdoc@npm:0.15.1"
- checksum: 10c0/09948691fac56c45a0d1920de478d66a30371a325bd81addc92eea5654d95106ce173c440fea1a1bd5bb95b3a544b6d4def7bb0b5a846c05d043575d8369a20c
+"@microsoft/tsdoc@npm:0.16.0, @microsoft/tsdoc@npm:~0.16.0":
+ version: 0.16.0
+ resolution: "@microsoft/tsdoc@npm:0.16.0"
+ checksum: 10c0/8883bb0ed22753af7360e9222687fda4eb448f0a574ea34b4596c11e320148b3ae0d24e00f8923df8ba7bc62a46a6f53b9343243a348640d923dfd55d52cd6bb
languageName: node
linkType: hard
@@ -6524,26 +6524,26 @@ __metadata:
languageName: node
linkType: hard
-"@vue/compiler-core@npm:3.5.23":
- version: 3.5.23
- resolution: "@vue/compiler-core@npm:3.5.23"
+"@vue/compiler-core@npm:3.5.24":
+ version: 3.5.24
+ resolution: "@vue/compiler-core@npm:3.5.24"
dependencies:
"@babel/parser": "npm:^7.28.5"
- "@vue/shared": "npm:3.5.23"
+ "@vue/shared": "npm:3.5.24"
entities: "npm:^4.5.0"
estree-walker: "npm:^2.0.2"
source-map-js: "npm:^1.2.1"
- checksum: 10c0/195c57b2eb8c6948bf3b1b3f65c2a5a9bf9e252376bcd22bd9b5e1787c4254abc4bffab5f15902c7820f5e607b26d44578cddeb39605ece37b611703c2d6152b
+ checksum: 10c0/d5b1421c0c0cfdff6b6ae2ef3d59b5901f0fec8ad2fa153f5ae1ec8487b898c92766353c661f68b892580ab0eacbc493632c946af8141045d6e76d67797b8a84
languageName: node
linkType: hard
"@vue/compiler-dom@npm:^3.5.0":
- version: 3.5.23
- resolution: "@vue/compiler-dom@npm:3.5.23"
+ version: 3.5.24
+ resolution: "@vue/compiler-dom@npm:3.5.24"
dependencies:
- "@vue/compiler-core": "npm:3.5.23"
- "@vue/shared": "npm:3.5.23"
- checksum: 10c0/fb925b2d64de40c1b39852f5fd26fdec3238f8381ccc2b30a1bef372ef894fff4e6f0231f8a135a02d6a5c8b8254dc7018bcd136a689579a72a3a0e1ff211a89
+ "@vue/compiler-core": "npm:3.5.24"
+ "@vue/shared": "npm:3.5.24"
+ checksum: 10c0/d49cb715f2e1cb2272ede2e41901282fb3f6fbdf489c8aa737e60c68e21216e07b72942695a80430fee8f11e5933e36fc90615b146b189cac925bf32f2727c95
languageName: node
linkType: hard
@@ -6578,10 +6578,10 @@ __metadata:
languageName: node
linkType: hard
-"@vue/shared@npm:3.5.23, @vue/shared@npm:^3.5.0":
- version: 3.5.23
- resolution: "@vue/shared@npm:3.5.23"
- checksum: 10c0/0f051ea60a756520b0b0af3d5058587b47f1942476c7f2cee6f78589c97c246acabdea11c73e2f84f13ecfb36c1160aacecca37694144326ebec8c108103bb89
+"@vue/shared@npm:3.5.24, @vue/shared@npm:^3.5.0":
+ version: 3.5.24
+ resolution: "@vue/shared@npm:3.5.24"
+ checksum: 10c0/4fd5665539fa5be3d12280c1921a8db3a707115fef54d22d83ce347ea06e3b1089dfe07292e0c46bbebf23553c7c1ec98010972ebccf10532db82422801288ff
languageName: node
linkType: hard
@@ -10099,9 +10099,9 @@ __metadata:
linkType: hard
"exsolve@npm:^1.0.7":
- version: 1.0.7
- resolution: "exsolve@npm:1.0.7"
- checksum: 10c0/4479369d0bd84bb7e0b4f5d9bc18d26a89b6dbbbccd73f9d383d14892ef78ddbe159e01781055342f83dc00ebe90044036daf17ddf55cc21e2cac6609aa15631
+ version: 1.0.8
+ resolution: "exsolve@npm:1.0.8"
+ checksum: 10c0/65e44ae05bd4a4a5d87cfdbbd6b8f24389282cf9f85fa5feb17ca87ad3f354877e6af4cd99e02fc29044174891f82d1d68c77f69234410eb8f163530e6278c67
languageName: node
linkType: hard
@@ -18670,8 +18670,8 @@ __metadata:
linkType: hard
"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0, vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0":
- version: 7.2.1
- resolution: "vite@npm:7.2.1"
+ version: 7.2.2
+ resolution: "vite@npm:7.2.2"
dependencies:
esbuild: "npm:^0.25.0"
fdir: "npm:^6.5.0"
@@ -18720,7 +18720,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
- checksum: 10c0/25fbcfc67b1598fa6152f3ed0a7355144a2ac203859ad3b6a2e466b0930ec1081c19cc6f0d83104897517ecf30c0aac3e4a50c4e5e2980d3659decb1d9e41a28
+ checksum: 10c0/9c76ee441f8dbec645ddaecc28d1f9cf35670ffa91cff69af7b1d5081545331603f0b1289d437b2fa8dc43cdc77b4d96b5bd9c9aed66310f490cb1a06f9c814c
languageName: node
linkType: hard