\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/App.tsx b/webui/src/App.tsx
index a3dd0e387..8e256437f 100644
--- a/webui/src/App.tsx
+++ b/webui/src/App.tsx
@@ -1,5 +1,5 @@
-import { Box, darkTheme, FaencyProvider, lightTheme } from '@traefiklabs/faency'
-import { Suspense, useEffect } from 'react'
+import { globalCss, Box, darkTheme, FaencyProvider, lightTheme } from '@traefiklabs/faency'
+import { Suspense, useContext, useEffect } from 'react'
import { HelmetProvider } from 'react-helmet-async'
import { HashRouter, Navigate, Route, Routes as RouterRoutes, useLocation } from 'react-router-dom'
import { SWRConfig } from 'swr'
@@ -12,6 +12,7 @@ import { useIsDarkMode } from 'hooks/use-theme'
import ErrorSuspenseWrapper from 'layout/ErrorSuspenseWrapper'
import { Dashboard, HTTPPages, NotFound, TCPPages, UDPPages } from 'pages'
import { DashboardSkeleton } from 'pages/dashboard/Dashboard'
+import { HubDemoContext, HubDemoProvider } from 'pages/hub-demo/demoNavContext'
export const LIGHT_THEME = lightTheme('blue')
export const DARK_THEME = darkTheme('blue')
@@ -33,44 +34,59 @@ const ScrollToTop = () => {
}
export const Routes = () => {
+ const { routes: hubDemoRoutes } = useContext(HubDemoContext)
+
return (
- }>
-
- }>
-
-
- }
- />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
+
+ }>
+
+ }>
+
+
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Hub Dashboard demo content */}
+ {hubDemoRoutes?.map((route, idx) => )}
+
+ } />
+
+
+
)
}
const isDev = import.meta.env.NODE_ENV === 'development'
+const customGlobalStyle = globalCss({
+ // target the AriaTd component, but exclude anything inside hub-ui-demo-app
+ 'body:not(:has(hub-ui-demo-app)) span[role=cell]': {
+ p: '$2 $3',
+ },
+})
+
const App = () => {
const isDarkMode = useIsDarkMode()
@@ -95,8 +111,11 @@ const App = () => {
>
-
-
+
+ {customGlobalStyle()}
+
+
+
diff --git a/webui/src/components/SpinnerLoader.tsx b/webui/src/components/SpinnerLoader.tsx
index 663130307..e336fc436 100644
--- a/webui/src/components/SpinnerLoader.tsx
+++ b/webui/src/components/SpinnerLoader.tsx
@@ -2,17 +2,17 @@ import { Flex } from '@traefiklabs/faency'
import { motion } from 'framer-motion'
import { FiLoader } from 'react-icons/fi'
-export const SpinnerLoader = () => (
+export const SpinnerLoader = ({ size = 24 }: { size?: number }) => (
-
+
)
diff --git a/webui/src/components/TableFilter.tsx b/webui/src/components/TableFilter.tsx
index ba0df2152..ab8c01938 100644
--- a/webui/src/components/TableFilter.tsx
+++ b/webui/src/components/TableFilter.tsx
@@ -1,6 +1,4 @@
-import { Box, Button, Flex, TextField } from '@traefiklabs/faency'
-// eslint-disable-next-line import/no-unresolved
-import { InputHandle } from '@traefiklabs/faency/dist/components/Input'
+import { Box, Button, Flex, TextField, InputHandle } from '@traefiklabs/faency'
import { isUndefined, omitBy } from 'lodash'
import { useCallback, useRef, useState } from 'react'
import { FiSearch, FiXCircle } from 'react-icons/fi'
diff --git a/webui/src/components/icons/providers/Knative.tsx b/webui/src/components/icons/providers/Knative.tsx
new file mode 100644
index 000000000..8eaf21d50
--- /dev/null
+++ b/webui/src/components/icons/providers/Knative.tsx
@@ -0,0 +1,13 @@
+import { ProviderIconProps } from 'components/icons/providers'
+
+export default function Knative(props: ProviderIconProps) {
+ return (
+
+ )
+}
diff --git a/webui/src/components/icons/providers/index.tsx b/webui/src/components/icons/providers/index.tsx
index 388c66090..8745a7644 100644
--- a/webui/src/components/icons/providers/index.tsx
+++ b/webui/src/components/icons/providers/index.tsx
@@ -8,6 +8,7 @@ import File from 'components/icons/providers/File'
import Http from 'components/icons/providers/Http'
import Hub from 'components/icons/providers/Hub'
import Internal from 'components/icons/providers/Internal'
+import Knative from 'components/icons/providers/Knative'
import Kubernetes from 'components/icons/providers/Kubernetes'
import Nomad from 'components/icons/providers/Nomad'
import Plugin from 'components/icons/providers/Plugin'
@@ -49,6 +50,9 @@ export default function ProviderIcon({ name, size = 32 }: { name: string; size?:
if (['kubernetes'].some((prefix) => nameLowerCase.startsWith(prefix))) {
return Kubernetes
}
+ if (['knative'].some((prefix) => nameLowerCase.startsWith(prefix))) {
+ return Knative
+ }
if (['nomad', 'nomad-'].some((prefix) => nameLowerCase.startsWith(prefix))) {
return Nomad
}
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/layout/Navigation.spec.tsx b/webui/src/layout/Navigation.spec.tsx
index d19ab884a..44b1a8704 100644
--- a/webui/src/layout/Navigation.spec.tsx
+++ b/webui/src/layout/Navigation.spec.tsx
@@ -1,8 +1,27 @@
+import { waitFor } from '@testing-library/react'
+
import { SideNav, TopNav } from './Navigation'
+import useHubUpgradeButton from 'hooks/use-hub-upgrade-button'
import { renderWithProviders } from 'utils/test'
+vi.mock('hooks/use-hub-upgrade-button')
+
+const mockUseHubUpgradeButton = vi.mocked(useHubUpgradeButton)
+
describe('Navigation', () => {
+ beforeEach(() => {
+ mockUseHubUpgradeButton.mockReturnValue({
+ signatureVerified: false,
+ scriptBlobUrl: null,
+ isCustomElementDefined: false,
+ })
+ })
+
+ afterEach(() => {
+ vi.clearAllMocks()
+ })
+
it('should render the side navigation bar', async () => {
const { container } = renderWithProviders( {}} />)
@@ -18,4 +37,60 @@ describe('Navigation', () => {
expect(container.innerHTML).toContain('theme-switcher')
expect(container.innerHTML).toContain('help-menu')
})
+
+ describe('hub-button-app rendering', () => {
+ it('should NOT render hub-button-app when signatureVerified is false', async () => {
+ mockUseHubUpgradeButton.mockReturnValue({
+ signatureVerified: false,
+ scriptBlobUrl: null,
+ isCustomElementDefined: false,
+ })
+
+ const { container } = renderWithProviders()
+
+ const hubButtonApp = container.querySelector('hub-button-app')
+ expect(hubButtonApp).toBeNull()
+ })
+
+ it('should NOT render hub-button-app when scriptBlobUrl is null', async () => {
+ mockUseHubUpgradeButton.mockReturnValue({
+ signatureVerified: true,
+ scriptBlobUrl: null,
+ isCustomElementDefined: false,
+ })
+
+ const { container } = renderWithProviders()
+
+ const hubButtonApp = container.querySelector('hub-button-app')
+ expect(hubButtonApp).toBeNull()
+ })
+
+ it('should render hub-button-app when signatureVerified is true and scriptBlobUrl exists', async () => {
+ mockUseHubUpgradeButton.mockReturnValue({
+ signatureVerified: true,
+ scriptBlobUrl: 'blob:http://localhost:3000/mock-blob-url',
+ isCustomElementDefined: false,
+ })
+
+ const { container } = renderWithProviders()
+
+ await waitFor(() => {
+ const hubButtonApp = container.querySelector('hub-button-app')
+ expect(hubButtonApp).not.toBeNull()
+ })
+ })
+
+ it('should NOT render hub-button-app when noHubButton prop is true', async () => {
+ mockUseHubUpgradeButton.mockReturnValue({
+ signatureVerified: true,
+ scriptBlobUrl: 'blob:http://localhost:3000/mock-blob-url',
+ isCustomElementDefined: false,
+ })
+
+ const { container } = renderWithProviders()
+
+ const hubButtonApp = container.querySelector('hub-button-app')
+ expect(hubButtonApp).toBeNull()
+ })
+ })
})
diff --git a/webui/src/layout/Navigation.tsx b/webui/src/layout/Navigation.tsx
index 62b29eedf..0f57405ab 100644
--- a/webui/src/layout/Navigation.tsx
+++ b/webui/src/layout/Navigation.tsx
@@ -2,6 +2,7 @@ import {
Badge,
Box,
Button,
+ CSS,
DialogTitle,
DropdownMenu,
DropdownMenuContent,
@@ -20,6 +21,7 @@ import {
VisuallyHidden,
} from '@traefiklabs/faency'
import { useContext, useEffect, useMemo, useState } from 'react'
+import { Helmet } from 'react-helmet-async'
import { BsChevronDoubleRight, BsChevronDoubleLeft } from 'react-icons/bs'
import { FiBookOpen, FiGithub, FiHelpCircle } from 'react-icons/fi'
import { matchPath, useHref } from 'react-router'
@@ -35,8 +37,10 @@ import { PluginsIcon } from 'components/icons/PluginsIcon'
import ThemeSwitcher from 'components/ThemeSwitcher'
import TooltipText from 'components/TooltipText'
import { VersionContext } from 'contexts/version'
+import useHubUpgradeButton from 'hooks/use-hub-upgrade-button'
import useTotals from 'hooks/use-overview-totals'
import { useIsDarkMode } from 'hooks/use-theme'
+import ApimDemoNavMenu from 'pages/hub-demo/HubDemoNav'
import { Route, ROUTES } from 'routes'
export const LAPTOP_BP = 1025
@@ -54,7 +58,7 @@ const NavigationDrawer = styled(Flex, {
},
})
-const BasicNavigationItem = ({
+export const BasicNavigationItem = ({
route,
count,
isSmallScreen,
@@ -270,7 +274,7 @@ export const SideNav = ({
))}
))}
-
+
}
css={{
@@ -283,14 +287,15 @@ export const SideNav = ({
{!isSmallScreen || isExpanded ? 'Plugins' : ''}
+
+
)
}
-export const TopNav = () => {
- const [hasHubButtonComponent, setHasHubButtonComponent] = useState(false)
- const { showHubButton, version } = useContext(VersionContext)
+export const TopNav = ({ css, noHubButton = false }: { css?: CSS; noHubButton?: boolean }) => {
+ const { version } = useContext(VersionContext)
const isDarkMode = useIsDarkMode()
const parsedVersion = useMemo(() => {
@@ -304,91 +309,73 @@ export const TopNav = () => {
return matches ? 'v' + matches[1] : 'master'
}, [version])
- useEffect(() => {
- if (!showHubButton) {
- setHasHubButtonComponent(false)
- return
- }
+ const { signatureVerified, scriptBlobUrl, isCustomElementDefined } = useHubUpgradeButton()
- if (customElements.get('hub-button-app')) {
- setHasHubButtonComponent(true)
- return
- }
-
- const scripts: HTMLScriptElement[] = []
- const createScript = (scriptSrc: string): HTMLScriptElement => {
- const script = document.createElement('script')
- script.src = scriptSrc
- script.async = true
- script.onload = () => {
- setHasHubButtonComponent(customElements.get('hub-button-app') !== undefined)
- }
- scripts.push(script)
- return script
- }
-
- // Source: https://github.com/traefik/traefiklabs-hub-button-app
- document.head.appendChild(createScript('traefiklabs-hub-button-app/main-v1.js'))
-
- return () => {
- // Remove the scripts on unmount.
- scripts.forEach((script) => {
- if (script.parentNode) {
- script.parentNode.removeChild(script)
- }
- })
- }
- }, [showHubButton])
+ const displayUpgradeToHubButton = useMemo(
+ () => !noHubButton && signatureVerified && (!!scriptBlobUrl || isCustomElementDefined),
+ [isCustomElementDefined, noHubButton, scriptBlobUrl, signatureVerified],
+ )
return (
-
- {hasHubButtonComponent && (
-
-
+ {displayUpgradeToHubButton && (
+
+
-
+
+
)}
-
+
+ {displayUpgradeToHubButton && (
+
+
+
+ )}
+
-
-
-
-
-
-
-
-
-
-
-
- Documentation
-
-
-
-
-
-
-
- Github Repository
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Documentation
+
+
+
+
+
+
+
+ Github Repository
+
+
+
+
+
+
+
+
+ >
)
}
diff --git a/webui/src/layout/Page.spec.tsx b/webui/src/layout/Page.spec.tsx
index 2334cdc31..b091ac30e 100644
--- a/webui/src/layout/Page.spec.tsx
+++ b/webui/src/layout/Page.spec.tsx
@@ -4,7 +4,7 @@ import { renderWithProviders } from 'utils/test'
describe('', () => {
it('should render an empty page', () => {
- const { getByTestId } = renderWithProviders()
- expect(getByTestId('Test page')).toBeInTheDocument()
+ const { getByTestId } = renderWithProviders(, { route: '/test' })
+ expect(getByTestId('/test page')).toBeInTheDocument()
})
})
diff --git a/webui/src/layout/Page.tsx b/webui/src/layout/Page.tsx
index f11afa091..ec8b6ea81 100644
--- a/webui/src/layout/Page.tsx
+++ b/webui/src/layout/Page.tsx
@@ -1,6 +1,7 @@
import { Flex, globalCss, styled } from '@traefiklabs/faency'
-import { ReactNode, useState } from 'react'
+import { ReactNode, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet-async'
+import { useLocation } from 'react-router-dom'
import Container from './Container'
import { LAPTOP_BP, SideBarPanel, SideNav, TopNav } from './Navigation'
@@ -40,14 +41,31 @@ export interface Props {
children?: ReactNode
}
-const Page = ({ children, title }: Props) => {
+const Page = ({ children }: Props) => {
+ const { pathname } = useLocation()
const [isSideBarPanelOpen, setIsSideBarPanelOpen] = useState(false)
+ const location = useLocation()
+
+ const isDemoPage = useMemo(() => pathname.includes('hub-dashboard'), [pathname])
+
+ const renderedContent = useMemo(() => {
+ if (isDemoPage) {
+ return children
+ }
+
+ return (
+
+
+ {children}
+
+ )
+ }, [children, isDemoPage, location.pathname])
return (
{globalStyles()}
- {title ? `${title} - ` : ''}Traefik Proxy
+ Traefik Proxy
@@ -56,10 +74,7 @@ const Page = ({ children, title }: Props) => {
justify="center"
css={{ flex: 1, margin: 'auto', ml: 264, [`@media (max-width:${LAPTOP_BP}px)`]: { ml: 60 } }}
>
-
-
- {children}
-
+ {renderedContent}
diff --git a/webui/src/pages/NotFound.tsx b/webui/src/pages/NotFound.tsx
index 51a130c4c..486b0b217 100644
--- a/webui/src/pages/NotFound.tsx
+++ b/webui/src/pages/NotFound.tsx
@@ -1,24 +1,24 @@
import { Box, Button, Flex, H1, Text } from '@traefiklabs/faency'
+import { Helmet } from 'react-helmet-async'
import { useNavigate } from 'react-router-dom'
-import Page from 'layout/Page'
-
export const NotFound = () => {
const navigate = useNavigate()
return (
-
-
-
- 404
-
-
- I'm sorry, nothing around here...
-
-
-
-
+
+
+ Not found - Traefik Proxy
+
+
+ 404
+
+
+ I'm sorry, nothing around here...
+
+
+
)
}
diff --git a/webui/src/pages/dashboard/Dashboard.tsx b/webui/src/pages/dashboard/Dashboard.tsx
index 9100823eb..b26cf0a9c 100644
--- a/webui/src/pages/dashboard/Dashboard.tsx
+++ b/webui/src/pages/dashboard/Dashboard.tsx
@@ -1,12 +1,12 @@
import { Card, CSS, Flex, Grid, H2, Text } from '@traefiklabs/faency'
import { ReactNode, useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import useSWR from 'swr'
import ProviderIcon from 'components/icons/providers'
import FeatureCard, { FeatureCardSkeleton } from 'components/resources/FeatureCard'
import ResourceCard from 'components/resources/ResourceCard'
import TraefikResourceStatsCard, { StatsCardSkeleton } from 'components/resources/TraefikResourceStatsCard'
-import Page from 'layout/Page'
import { capitalizeFirstLetter } from 'utils/string'
const RESOURCES = ['routers', 'services', 'middlewares']
@@ -76,159 +76,158 @@ export const Dashboard = () => {
}
return (
-
-
-
- {entrypoints?.map((i, idx) => (
-
- {i.address}
-
- ))}
-
+
+
+ Dashboard - Traefik Proxy
+
+
+ {entrypoints?.map((i, idx) => (
+
+ {i.address}
+
+ ))}
+
-
- {overview?.http && hasResources.http ? (
- RESOURCES.map((i) => (
-
- ))
- ) : (
- No related objects to show.
- )}
-
+
+ {overview?.http && hasResources.http ? (
+ RESOURCES.map((i) => (
+
+ ))
+ ) : (
+ No related objects to show.
+ )}
+
-
- {overview?.tcp && hasResources.tcp ? (
- RESOURCES.map((i) => (
-
- ))
- ) : (
- No related objects to show.
- )}
-
+
+ {overview?.tcp && hasResources.tcp ? (
+ RESOURCES.map((i) => (
+
+ ))
+ ) : (
+ No related objects to show.
+ )}
+
-
- {overview?.udp && hasResources.udp ? (
- RESOURCES.map((i) => (
-
- ))
- ) : (
- No related objects to show.
- )}
-
+
+ {overview?.udp && hasResources.udp ? (
+ RESOURCES.map((i) => (
+
+ ))
+ ) : (
+ No related objects to show.
+ )}
+
-
- {features.length
- ? features.map((i, idx) => {
- return
- })
- : null}
-
+
+ {features.length
+ ? features.map((i, idx) => {
+ return
+ })
+ : null}
+
-
- {overview?.providers?.length ? (
- overview.providers.map((p, idx) => (
-
-
-
- {p}
-
-
- ))
- ) : (
- No related objects to show.
- )}
-
-
-
+
+ {overview?.providers?.length ? (
+ overview.providers.map((p, idx) => (
+
+
+
+ {p}
+
+
+ ))
+ ) : (
+ No related objects to show.
+ )}
+
+
)
}
export const DashboardSkeleton = () => {
return (
-
-
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
+
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
-
- {[...Array(3)].map((_, i) => (
-
- ))}
-
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
-
- {[...Array(3)].map((_, i) => (
-
- ))}
-
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
-
- {[...Array(3)].map((_, i) => (
-
- ))}
-
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
-
- {[...Array(3)].map((_, i) => (
-
- ))}
-
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
-
- {[...Array(3)].map((_, i) => (
-
- ))}
-
-
-
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
+
)
}
diff --git a/webui/src/pages/http/HttpMiddleware.spec.tsx b/webui/src/pages/http/HttpMiddleware.spec.tsx
index d7a7c39bb..8bf003735 100644
--- a/webui/src/pages/http/HttpMiddleware.spec.tsx
+++ b/webui/src/pages/http/HttpMiddleware.spec.tsx
@@ -7,6 +7,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/middlewares/mock-middleware', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -14,6 +15,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/middlewares/mock-middleware', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -21,6 +23,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/middlewares/mock-middleware', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -53,6 +56,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/middlewares/middleware-simple', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
@@ -99,6 +103,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/middlewares/middleware-plugin', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
@@ -338,6 +343,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/middlewares/middleware-complex', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
@@ -459,6 +465,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/middlewares/middleware-plugin-no-type', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
diff --git a/webui/src/pages/http/HttpMiddleware.tsx b/webui/src/pages/http/HttpMiddleware.tsx
index 4b2fe7e2a..d762e6975 100644
--- a/webui/src/pages/http/HttpMiddleware.tsx
+++ b/webui/src/pages/http/HttpMiddleware.tsx
@@ -1,11 +1,11 @@
import { Box, Card, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
+import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import { DetailSectionSkeleton } from 'components/resources/DetailSections'
import { RenderMiddleware } from 'components/resources/MiddlewarePanel'
import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection'
import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
import { NotFound } from 'pages/NotFound'
import breakpoints from 'utils/breakpoints'
@@ -27,23 +27,29 @@ type HttpMiddlewareRenderProps = {
export const HttpMiddlewareRender = ({ data, error, name }: HttpMiddlewareRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Middleware right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
-
+ >
)
}
@@ -52,7 +58,10 @@ export const HttpMiddlewareRender = ({ data, error, name }: HttpMiddlewareRender
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
{data.name}
@@ -60,7 +69,7 @@ export const HttpMiddlewareRender = ({ data, error, name }: HttpMiddlewareRender
-
+ >
)
}
diff --git a/webui/src/pages/http/HttpMiddlewares.spec.tsx b/webui/src/pages/http/HttpMiddlewares.spec.tsx
index 1f4ce2607..b49b42062 100644
--- a/webui/src/pages/http/HttpMiddlewares.spec.tsx
+++ b/webui/src/pages/http/HttpMiddlewares.spec.tsx
@@ -76,10 +76,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/http/middlewares',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('HTTP Middlewares page')).toBeInTheDocument()
+ expect(getByTestId('/http/middlewares page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(5)
@@ -120,6 +123,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/http/middlewares', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/http/HttpMiddlewares.tsx b/webui/src/pages/http/HttpMiddlewares.tsx
index f61a4ed6b..4ec86d841 100644
--- a/webui/src/pages/http/HttpMiddlewares.tsx
+++ b/webui/src/pages/http/HttpMiddlewares.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -13,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
import { parseMiddlewareType } from 'libs/parsers'
export const makeRowRender = (): RenderRowType => {
@@ -79,9 +79,7 @@ export const HttpMiddlewaresRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -109,7 +107,10 @@ export const HttpMiddlewares = () => {
)
return (
-
+ <>
+
+ HTTP Middlewares - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/pages/http/HttpRouter.spec.tsx b/webui/src/pages/http/HttpRouter.spec.tsx
index a7f1e3ad7..ca90455d9 100644
--- a/webui/src/pages/http/HttpRouter.spec.tsx
+++ b/webui/src/pages/http/HttpRouter.spec.tsx
@@ -10,6 +10,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/routers/mock-router', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -17,6 +18,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/routers/mock-router', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -24,6 +26,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/routers/mock-router', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -40,6 +43,7 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/routers/orphan-router@file', withPage: true },
)
const routerStructure = getByTestId('router-structure')
diff --git a/webui/src/pages/http/HttpRouter.tsx b/webui/src/pages/http/HttpRouter.tsx
index dbb493e4d..62801083a 100644
--- a/webui/src/pages/http/HttpRouter.tsx
+++ b/webui/src/pages/http/HttpRouter.tsx
@@ -1,5 +1,6 @@
import { Flex, styled, Text } from '@traefiklabs/faency'
import { useContext, useEffect, useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import { FiGlobe, FiLayers, FiLogIn, FiZap } from 'react-icons/fi'
import { useParams } from 'react-router-dom'
@@ -9,7 +10,6 @@ import RouterPanel from 'components/resources/RouterPanel'
import TlsPanel from 'components/resources/TlsPanel'
import { ToastContext } from 'contexts/toasts'
import { EntryPoint, ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
import { getErrorData, getValidData } from 'libs/objectHandlers'
import { parseMiddlewareType } from 'libs/parsers'
import { NotFound } from 'pages/NotFound'
@@ -105,17 +105,23 @@ type HttpRouterRenderProps = {
export const HttpRouterRender = ({ data, error, name }: HttpRouterRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Router right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
@@ -127,7 +133,7 @@ export const HttpRouterRender = ({ data, error, name }: HttpRouterRenderProps) =
-
+ >
)
}
@@ -136,10 +142,13 @@ export const HttpRouterRender = ({ data, error, name }: HttpRouterRenderProps) =
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
-
+ >
)
}
diff --git a/webui/src/pages/http/HttpRouters.spec.tsx b/webui/src/pages/http/HttpRouters.spec.tsx
index bdddd21b4..a4df44257 100644
--- a/webui/src/pages/http/HttpRouters.spec.tsx
+++ b/webui/src/pages/http/HttpRouters.spec.tsx
@@ -49,10 +49,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/http/routers',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('HTTP Routers page')).toBeInTheDocument()
+ expect(getByTestId('/http/routers page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(4)
@@ -100,6 +103,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/http/routers', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/http/HttpRouters.tsx b/webui/src/pages/http/HttpRouters.tsx
index d646b8f42..7896ca38a 100644
--- a/webui/src/pages/http/HttpRouters.tsx
+++ b/webui/src/pages/http/HttpRouters.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import { FiShield } from 'react-icons/fi'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -15,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (protocol = 'http'): RenderRowType => {
const HttpRoutersRenderRow = (row) => (
@@ -100,9 +100,7 @@ export const HttpRoutersRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -130,7 +128,10 @@ export const HttpRouters = () => {
)
return (
-
+ <>
+
+ HTTP Routers - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/pages/http/HttpService.spec.tsx b/webui/src/pages/http/HttpService.spec.tsx
index 781d43099..76059e21d 100644
--- a/webui/src/pages/http/HttpService.spec.tsx
+++ b/webui/src/pages/http/HttpService.spec.tsx
@@ -7,6 +7,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/services/mock-service', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -14,6 +15,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/services/mock-service', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -21,6 +23,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/http/services/mock-service', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -71,6 +74,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/services/mock-service', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
@@ -142,6 +146,7 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/services/mock-service', withPage: true },
)
const healthCheck = getByTestId('health-check')
@@ -196,6 +201,7 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/http/services/mock-service', withPage: true },
)
const mirrorServices = getByTestId('mirror-services')
diff --git a/webui/src/pages/http/HttpService.tsx b/webui/src/pages/http/HttpService.tsx
index 4e74c552f..1761cbf05 100644
--- a/webui/src/pages/http/HttpService.tsx
+++ b/webui/src/pages/http/HttpService.tsx
@@ -1,5 +1,6 @@
import { Badge, Box, Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import { FiGlobe, FiInfo, FiShield } from 'react-icons/fi'
import { useParams } from 'react-router-dom'
@@ -18,7 +19,6 @@ import { ResourceStatus } from 'components/resources/ResourceStatus'
import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection'
import Tooltip from 'components/Tooltip'
import { ResourceDetailDataType, ServiceDetailType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
import { NotFound } from 'pages/NotFound'
type DetailProps = {
@@ -270,17 +270,23 @@ type HttpServiceRenderProps = {
export const HttpServiceRender = ({ data, error, name }: HttpServiceRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Service right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
@@ -288,7 +294,7 @@ export const HttpServiceRender = ({ data, error, name }: HttpServiceRenderProps)
-
+ >
)
}
@@ -297,11 +303,14 @@ export const HttpServiceRender = ({ data, error, name }: HttpServiceRenderProps)
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
{data.name}
-
+ >
)
}
diff --git a/webui/src/pages/http/HttpServices.spec.tsx b/webui/src/pages/http/HttpServices.spec.tsx
index 720fc2549..31a749c8c 100644
--- a/webui/src/pages/http/HttpServices.spec.tsx
+++ b/webui/src/pages/http/HttpServices.spec.tsx
@@ -49,10 +49,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/http/services',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('HTTP Services page')).toBeInTheDocument()
+ expect(getByTestId('/http/services page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(4)
@@ -92,6 +95,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/http/services', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/http/HttpServices.tsx b/webui/src/pages/http/HttpServices.tsx
index 6febd6b1f..e49bc82b6 100644
--- a/webui/src/pages/http/HttpServices.tsx
+++ b/webui/src/pages/http/HttpServices.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -13,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const HttpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const HttpServicesRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -108,7 +106,10 @@ export const HttpServices = () => {
)
return (
-
+ <>
+
+ HTTP Services - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/pages/hub-demo/HubDashboard.spec.tsx b/webui/src/pages/hub-demo/HubDashboard.spec.tsx
new file mode 100644
index 000000000..f5b479586
--- /dev/null
+++ b/webui/src/pages/hub-demo/HubDashboard.spec.tsx
@@ -0,0 +1,204 @@
+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 { renderWithProviders } from 'utils/test'
+import verifySignature from 'utils/workers/scriptVerification'
+
+vi.mock('utils/workers/scriptVerification', () => ({
+ default: vi.fn(),
+}))
+
+vi.mock('react-router-dom', async () => {
+ const actual = await vi.importActual('react-router-dom')
+ return {
+ ...actual,
+ useParams: vi.fn(() => ({ id: 'test-id' })),
+ }
+})
+
+vi.mock('hooks/use-theme', () => ({
+ useIsDarkMode: vi.fn(() => false),
+ useTheme: vi.fn(() => ({
+ selectedTheme: 'light',
+ appliedTheme: 'light',
+ setTheme: vi.fn(),
+ })),
+}))
+
+describe('HubDashboard demo', () => {
+ const mockVerifyScriptSignature = vi.mocked(verifySignature)
+ let mockCreateObjectURL: ReturnType
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+
+ mockCreateObjectURL = vi.fn(() => 'blob:mock-url')
+ globalThis.URL.createObjectURL = mockCreateObjectURL
+ })
+
+ afterEach(() => {
+ vi.restoreAllMocks()
+ })
+
+ describe('without cache', () => {
+ beforeEach(() => {
+ resetCache()
+ })
+
+ it('should render loading state during script verification', async () => {
+ const mockScriptContent = new ArrayBuffer(100)
+ mockVerifyScriptSignature.mockImplementation(
+ () =>
+ new Promise((resolve) =>
+ setTimeout(() => resolve({ verified: true, scriptContent: mockScriptContent }), 100),
+ ),
+ )
+
+ const { getByTestId } = renderWithProviders(, {
+ route: '/hub-dashboard',
+ })
+
+ expect(getByTestId('loading')).toBeInTheDocument()
+
+ await waitFor(() => {
+ expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ it('should render the custom web component when signature is verified', async () => {
+ const mockScriptContent = new ArrayBuffer(100)
+ mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
+
+ const { container } = renderWithProviders(, {
+ route: '/hub-dashboard',
+ })
+
+ await waitFor(() => {
+ expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
+ })
+
+ const hubComponent = container.querySelector('hub-ui-demo-app')
+ expect(hubComponent).toBeInTheDocument()
+ expect(hubComponent?.getAttribute('path')).toBe('dashboard')
+ expect(hubComponent?.getAttribute('baseurl')).toBe('#/hub-dashboard')
+ expect(hubComponent?.getAttribute('theme')).toBe('light')
+ })
+
+ it('should render error state when signature verification fails', async () => {
+ mockVerifyScriptSignature.mockResolvedValue({ verified: false })
+
+ const { container } = renderWithProviders()
+
+ await waitFor(() => {
+ expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
+ })
+
+ expect(container.textContent).toContain("Oops! We couldn't load the demo content")
+
+ const errorImage = container.querySelector('img[src="/img/gopher-something-went-wrong.png"]')
+ expect(errorImage).toBeInTheDocument()
+
+ const links = container.querySelectorAll('a')
+ const websiteLink = Array.from(links).find((link) => link.href.includes('traefik.io/traefik-hub'))
+ const docLink = Array.from(links).find((link) => link.href.includes('doc.traefik.io/traefik-hub'))
+
+ expect(websiteLink).toBeInTheDocument()
+ expect(docLink).toBeInTheDocument()
+ })
+
+ it('should render error state when verification throws an error', async () => {
+ mockVerifyScriptSignature.mockRejectedValue(new Error('Network error'))
+
+ const { container } = renderWithProviders()
+
+ await waitFor(() => {
+ expect(container.textContent).toContain("Oops! We couldn't load the demo content")
+ })
+ })
+
+ it('should call verifyScriptSignature with correct parameters', async () => {
+ const mockScriptContent = new ArrayBuffer(100)
+ mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
+
+ renderWithProviders()
+
+ await waitFor(() => {
+ expect(mockVerifyScriptSignature).toHaveBeenCalledWith(
+ 'https://assets.traefik.io/hub-ui-demo.js',
+ 'https://assets.traefik.io/hub-ui-demo.js.sig',
+ PUBLIC_KEY,
+ )
+ })
+ })
+
+ it('should set theme attribute based on dark mode', async () => {
+ const mockScriptContent = new ArrayBuffer(100)
+ mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
+
+ const { container } = renderWithProviders()
+
+ await waitFor(() => {
+ expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
+ })
+
+ const hubComponent = container.querySelector('hub-ui-demo-app')
+
+ expect(hubComponent?.getAttribute('theme')).toMatch(/light|dark/)
+ })
+
+ it('should handle path with :id parameter correctly', async () => {
+ const mockScriptContent = new ArrayBuffer(100)
+ mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
+
+ const { container } = renderWithProviders()
+
+ await waitFor(() => {
+ expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
+ })
+
+ const hubComponent = container.querySelector('hub-ui-demo-app')
+ expect(hubComponent).toBeInTheDocument()
+ expect(hubComponent?.getAttribute('path')).toBe('gateways/test-id')
+ })
+ })
+
+ describe('with cache', () => {
+ beforeEach(() => {
+ resetCache()
+ })
+
+ it('should use cached blob URL without calling verifySignature again', async () => {
+ const mockScriptContent = new ArrayBuffer(100)
+ mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
+
+ // First render
+ const { container: firstContainer, unmount: firstUnmount } = renderWithProviders(
+ ,
+ )
+
+ await waitFor(() => {
+ expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
+ })
+
+ const firstHubComponent = firstContainer.querySelector('hub-ui-demo-app')
+ expect(firstHubComponent).toBeInTheDocument()
+
+ firstUnmount()
+
+ mockVerifyScriptSignature.mockClear()
+
+ // Second render - should use cache
+ const { container: secondContainer } = renderWithProviders()
+
+ await waitFor(() => {
+ const secondHubComponent = secondContainer.querySelector('hub-ui-demo-app')
+ expect(secondHubComponent).toBeInTheDocument()
+ })
+
+ expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(0)
+ })
+ })
+})
diff --git a/webui/src/pages/hub-demo/HubDashboard.tsx b/webui/src/pages/hub-demo/HubDashboard.tsx
new file mode 100644
index 000000000..9dd14c9c8
--- /dev/null
+++ b/webui/src/pages/hub-demo/HubDashboard.tsx
@@ -0,0 +1,149 @@
+import { Box, Flex, Image, Link, Text } from '@traefiklabs/faency'
+import { useMemo, useEffect, useState } from 'react'
+import { Helmet } from 'react-helmet-async'
+import { useParams } from 'react-router-dom'
+
+import verifySignature from '../../utils/workers/scriptVerification'
+
+import { PUBLIC_KEY } from './constants'
+
+import { SpinnerLoader } from 'components/SpinnerLoader'
+import { useIsDarkMode } from 'hooks/use-theme'
+import { TopNav } from 'layout/Navigation'
+
+const SCRIPT_URL = 'https://assets.traefik.io/hub-ui-demo.js'
+
+// Module-level cache to persist across component mount/unmount
+let cachedBlobUrl: string | null = null
+
+// Export a function to reset the cache (for testing)
+export const resetCache = () => {
+ cachedBlobUrl = null
+}
+
+const HubDashboard = ({ path }: { path: string }) => {
+ const isDarkMode = useIsDarkMode()
+ const [scriptError, setScriptError] = useState(undefined)
+ const [signatureVerified, setSignatureVerified] = useState(false)
+ const [verificationInProgress, setVerificationInProgress] = useState(false)
+ const [scriptBlobUrl, setScriptBlobUrl] = useState(null)
+
+ const { id } = useParams()
+
+ const usedPath = useMemo(() => {
+ if (path?.includes(':id')) {
+ const splitted = path.split(':')
+ return `${splitted[0]}/${id}`
+ }
+
+ return path
+ }, [id, path])
+
+ useEffect(() => {
+ const verifyAndLoadScript = async () => {
+ setVerificationInProgress(true)
+
+ try {
+ const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`, PUBLIC_KEY)
+
+ if (!verified || !content) {
+ setScriptError(true)
+ setVerificationInProgress(false)
+ } else {
+ setScriptError(false)
+
+ const blob = new Blob([content], { type: 'application/javascript' })
+ cachedBlobUrl = URL.createObjectURL(blob)
+
+ setScriptBlobUrl(cachedBlobUrl)
+ setSignatureVerified(true)
+ setVerificationInProgress(false)
+ }
+ } catch {
+ setScriptError(true)
+ setVerificationInProgress(false)
+ }
+ }
+
+ if (!cachedBlobUrl) {
+ verifyAndLoadScript()
+ } else {
+ setScriptBlobUrl(cachedBlobUrl)
+ setSignatureVerified(true)
+ }
+ }, [])
+
+ if (scriptError && !verificationInProgress) {
+ return (
+
+
+ Oops! We couldn't load the demo content.
+
+ Don't worry — you can still learn more about{' '}
+
+ Traefik Hub API Management
+ {' '}
+ on our{' '}
+
+ website
+ {' '}
+ or in our{' '}
+
+ documentation
+
+ .
+
+
+ )
+ }
+
+ return (
+
+
+ Hub Demo - Traefik Proxy
+
+ {signatureVerified && scriptBlobUrl && }
+
+
+
+
+ {verificationInProgress ? (
+
+
+
+ ) : (
+
+ )}
+
+ )
+}
+
+export default HubDashboard
diff --git a/webui/src/pages/hub-demo/HubDemoNav.tsx b/webui/src/pages/hub-demo/HubDemoNav.tsx
new file mode 100644
index 000000000..eba41dc07
--- /dev/null
+++ b/webui/src/pages/hub-demo/HubDemoNav.tsx
@@ -0,0 +1,84 @@
+import { Badge, Box, Flex, Text } from '@traefiklabs/faency'
+import { useContext, useState } from 'react'
+import { BsChevronRight } from 'react-icons/bs'
+
+import { HubDemoContext } from './demoNavContext'
+import { HubIcon } from './icons'
+
+import Tooltip from 'components/Tooltip'
+import { BasicNavigationItem, LAPTOP_BP } from 'layout/Navigation'
+
+const ApimDemoNavMenu = ({
+ isResponsive,
+ isSmallScreen,
+ isExpanded,
+}: {
+ isResponsive: boolean
+ isSmallScreen: boolean
+ isExpanded: boolean
+}) => {
+ const [isCollapsed, setIsCollapsed] = useState(false)
+ const { navigationItems: hubDemoNavItems } = useContext(HubDemoContext)
+
+ if (!hubDemoNavItems) {
+ return null
+ }
+
+ return (
+
+ setIsCollapsed(!isCollapsed)}
+ >
+
+ {isSmallScreen ? (
+
+
+
+
+
+ ) : (
+ <>
+
+ API management
+
+
+ Demo
+
+ >
+ )}
+
+
+
+ {hubDemoNavItems.map((route, idx) => (
+
+ ))}
+
+
+ )
+}
+
+export default ApimDemoNavMenu
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/demoNavContext.tsx b/webui/src/pages/hub-demo/demoNavContext.tsx
new file mode 100644
index 000000000..3f28afc78
--- /dev/null
+++ b/webui/src/pages/hub-demo/demoNavContext.tsx
@@ -0,0 +1,15 @@
+import { createContext } from 'react'
+import { RouteObject } from 'react-router-dom'
+
+import { useHubDemo } from './use-hub-demo'
+
+export const HubDemoContext = createContext<{
+ routes: RouteObject[] | null
+ navigationItems: HubDemo.NavItem[] | null
+}>({ routes: null, navigationItems: null })
+
+export const HubDemoProvider = ({ basePath, children }) => {
+ const { routes, navigationItems } = useHubDemo(basePath)
+
+ return {children}
+}
diff --git a/webui/src/pages/hub-demo/hub-demo.d.ts b/webui/src/pages/hub-demo/hub-demo.d.ts
new file mode 100644
index 000000000..31c6cfde7
--- /dev/null
+++ b/webui/src/pages/hub-demo/hub-demo.d.ts
@@ -0,0 +1,21 @@
+namespace HubDemo {
+ interface Route {
+ path: string
+ label: string
+ icon: string
+ contentPath: string
+ dynamicSegments?: string[]
+ activeMatches?: string[]
+ }
+
+ interface Manifest {
+ routes: Route[]
+ }
+
+ interface NavItem {
+ path: string
+ label: string
+ icon: ReactNode
+ activeMatches?: string[]
+ }
+}
diff --git a/webui/src/pages/hub-demo/icons/api.tsx b/webui/src/pages/hub-demo/icons/api.tsx
new file mode 100644
index 000000000..04b51da6f
--- /dev/null
+++ b/webui/src/pages/hub-demo/icons/api.tsx
@@ -0,0 +1,68 @@
+import { Flex } from '@traefiklabs/faency'
+import { useId } from 'react'
+
+import { CustomIconProps } from 'components/icons'
+
+const ApiIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => {
+ const linearGradient1Id = useId()
+ const linearGradient2Id = useId()
+ const linearGradient3Id = useId()
+ const titleId = useId()
+
+ return (
+
+
+
+ )
+}
+
+export default ApiIcon
diff --git a/webui/src/pages/hub-demo/icons/dashboard.tsx b/webui/src/pages/hub-demo/icons/dashboard.tsx
new file mode 100644
index 000000000..44ab47aa6
--- /dev/null
+++ b/webui/src/pages/hub-demo/icons/dashboard.tsx
@@ -0,0 +1,28 @@
+import { Flex } from '@traefiklabs/faency'
+
+import { CustomIconProps } from 'components/icons'
+
+const DashboardIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => {
+ return (
+
+
+
+ )
+}
+
+export default DashboardIcon
diff --git a/webui/src/pages/hub-demo/icons/gateway.tsx b/webui/src/pages/hub-demo/icons/gateway.tsx
new file mode 100644
index 000000000..d1f682bf4
--- /dev/null
+++ b/webui/src/pages/hub-demo/icons/gateway.tsx
@@ -0,0 +1,69 @@
+import { Flex } from '@traefiklabs/faency'
+import { useId } from 'react'
+
+import { CustomIconProps } from 'components/icons'
+
+const GatewayIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => {
+ const titleId = useId()
+
+ return (
+
+
+
+ )
+}
+
+export default GatewayIcon
diff --git a/webui/src/pages/hub-demo/icons/hub.tsx b/webui/src/pages/hub-demo/icons/hub.tsx
new file mode 100644
index 000000000..64050576f
--- /dev/null
+++ b/webui/src/pages/hub-demo/icons/hub.tsx
@@ -0,0 +1,18 @@
+import { CustomIconProps } from 'components/icons'
+
+const Hub = (props: CustomIconProps) => {
+ const { color = 'currentColor', ...restProps } = props
+
+ return (
+
+ )
+}
+
+export default Hub
diff --git a/webui/src/pages/hub-demo/icons/index.ts b/webui/src/pages/hub-demo/icons/index.ts
new file mode 100644
index 000000000..5118c0fe4
--- /dev/null
+++ b/webui/src/pages/hub-demo/icons/index.ts
@@ -0,0 +1,5 @@
+export { default as ApiIcon } from './api'
+export { default as DashboardIcon } from './dashboard'
+export { default as GatewayIcon } from './gateway'
+export { default as HubIcon } from './hub'
+export { default as PortalIcon } from './portal'
diff --git a/webui/src/pages/hub-demo/icons/portal.tsx b/webui/src/pages/hub-demo/icons/portal.tsx
new file mode 100644
index 000000000..a21413ce8
--- /dev/null
+++ b/webui/src/pages/hub-demo/icons/portal.tsx
@@ -0,0 +1,48 @@
+import { Flex } from '@traefiklabs/faency'
+import { useId } from 'react'
+
+import { CustomIconProps } from 'components/icons'
+
+const PortalIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => {
+ const linearGradientId = useId()
+ const titleId = useId()
+
+ return (
+
+
+
+ )
+}
+
+export default PortalIcon
diff --git a/webui/src/pages/hub-demo/use-hub-demo.spec.tsx b/webui/src/pages/hub-demo/use-hub-demo.spec.tsx
new file mode 100644
index 000000000..6d77c1d39
--- /dev/null
+++ b/webui/src/pages/hub-demo/use-hub-demo.spec.tsx
@@ -0,0 +1,302 @@
+import { renderHook, waitFor } from '@testing-library/react'
+import { ReactNode } from 'react'
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
+
+import { useHubDemo } from './use-hub-demo'
+
+import verifySignature from 'utils/workers/scriptVerification'
+
+vi.mock('utils/workers/scriptVerification', () => ({
+ default: vi.fn(),
+}))
+
+const MOCK_ROUTES_MANIFEST = {
+ routes: [
+ {
+ path: '/dashboard',
+ label: 'Dashboard',
+ icon: 'dashboard',
+ contentPath: 'dashboard',
+ },
+ {
+ path: '/gateway',
+ label: 'Gateway',
+ icon: 'gateway',
+ contentPath: 'gateway',
+ dynamicSegments: [':id'],
+ activeMatches: ['/gateway/:id'],
+ },
+ ],
+}
+
+describe('useHubDemo', () => {
+ const mockVerifySignature = vi.mocked(verifySignature)
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ afterEach(() => {
+ vi.restoreAllMocks()
+ })
+
+ const setupMockVerification = (manifest: HubDemo.Manifest) => {
+ const encoder = new TextEncoder()
+ const mockScriptContent = encoder.encode(JSON.stringify(manifest))
+
+ mockVerifySignature.mockResolvedValue({
+ verified: true,
+ scriptContent: mockScriptContent.buffer,
+ })
+ }
+
+ describe('basic functions', () => {
+ const mockVerifySignature = vi.mocked(verifySignature)
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ afterEach(() => {
+ vi.restoreAllMocks()
+ })
+
+ it('should return null when signature verification fails', async () => {
+ mockVerifySignature.mockResolvedValue({
+ verified: false,
+ })
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(mockVerifySignature).toHaveBeenCalled()
+ })
+
+ await new Promise((resolve) => setTimeout(resolve, 10))
+
+ expect(result.current.routes).toBeNull()
+ expect(result.current.navigationItems).toBeNull()
+ })
+
+ it('should return null when scriptContent is missing', async () => {
+ mockVerifySignature.mockResolvedValue({
+ verified: true,
+ scriptContent: undefined,
+ })
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(mockVerifySignature).toHaveBeenCalled()
+ })
+
+ await new Promise((resolve) => setTimeout(resolve, 10))
+
+ expect(result.current.routes).toBeNull()
+ expect(result.current.navigationItems).toBeNull()
+ })
+
+ it('should handle errors during manifest fetch', async () => {
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
+
+ mockVerifySignature.mockRejectedValue(new Error('Network error'))
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to load hub demo manifest:', expect.any(Error))
+ })
+
+ expect(result.current.routes).toBeNull()
+ expect(result.current.navigationItems).toBeNull()
+
+ consoleErrorSpy.mockRestore()
+ })
+
+ it('should handle invalid JSON in manifest', async () => {
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
+
+ const encoder = new TextEncoder()
+ const invalidJson = encoder.encode('{ invalid json }')
+
+ mockVerifySignature.mockResolvedValue({
+ verified: true,
+ scriptContent: invalidJson.buffer,
+ })
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to load hub demo manifest:', expect.any(Error))
+ })
+
+ expect(result.current.routes).toBeNull()
+ expect(result.current.navigationItems).toBeNull()
+
+ consoleErrorSpy.mockRestore()
+ })
+ })
+
+ describe('routes generation', () => {
+ it('should generate routes with correct base path', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(result.current.routes).not.toBeNull()
+ })
+
+ expect(result.current.routes).toHaveLength(3)
+ expect(result.current.routes![0].path).toBe('/hub/dashboard')
+ expect(result.current.routes![1].path).toBe('/hub/gateway')
+ expect(result.current.routes![2].path).toBe('/hub/gateway/:id')
+ })
+
+ it('should generate routes for dynamic segments', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(result.current.routes).not.toBeNull()
+ })
+
+ expect(result.current.routes).toHaveLength(3)
+ expect(result.current.routes![0].path).toBe('/hub/dashboard')
+ expect(result.current.routes![1].path).toBe('/hub/gateway')
+ expect(result.current.routes![2].path).toBe('/hub/gateway/:id')
+ })
+
+ it('should render HubDashboard with correct contentPath for dynamic segments', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(result.current.routes).not.toBeNull()
+ })
+
+ const baseRoute = result.current.routes![1]
+ const dynamicRoute = result.current.routes![2]
+
+ expect(baseRoute.element).toBeDefined()
+ expect(dynamicRoute.element).toBeDefined()
+
+ const baseElement = baseRoute.element as ReactNode & { props?: { path: string } }
+ const dynamicElement = dynamicRoute.element as ReactNode & { props?: { path: string } }
+
+ expect((baseElement as { props: { path: string } }).props.path).toBe('gateway')
+ expect((dynamicElement as { props: { path: string } }).props.path).toBe('gateway:id')
+ })
+
+ it('should update routes when basePath changes', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result, rerender } = renderHook(({ basePath }) => useHubDemo(basePath), {
+ initialProps: { basePath: '/hub' },
+ })
+
+ await waitFor(() => {
+ expect(result.current.routes).not.toBeNull()
+ })
+
+ expect(result.current.routes![0].path).toBe('/hub/dashboard')
+
+ rerender({ basePath: '/demo' })
+
+ expect(result.current.routes![0].path).toBe('/demo/dashboard')
+ })
+ })
+
+ describe('navigation items generation', () => {
+ it('should generate navigation items with correct icons', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(result.current.navigationItems).not.toBeNull()
+ })
+
+ expect(result.current.navigationItems).toHaveLength(2)
+ expect(result.current.navigationItems![0].label).toBe('Dashboard')
+ expect(result.current.navigationItems![0].path).toBe('/hub/dashboard')
+ expect(result.current.navigationItems![0].icon).toBeDefined()
+ expect(result.current.navigationItems![1].label).toBe('Gateway')
+ })
+
+ it('should include activeMatches in navigation items', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(result.current.navigationItems).not.toBeNull()
+ })
+
+ expect(result.current.navigationItems![1].activeMatches).toEqual(['/hub/gateway/:id'])
+ })
+
+ it('should update navigation items when basePath changes', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result, rerender } = renderHook(({ basePath }) => useHubDemo(basePath), {
+ initialProps: { basePath: '/hub' },
+ })
+
+ await waitFor(() => {
+ expect(result.current.navigationItems).not.toBeNull()
+ })
+
+ expect(result.current.navigationItems![0].path).toBe('/hub/dashboard')
+
+ rerender({ basePath: '/demo' })
+
+ expect(result.current.navigationItems![0].path).toBe('/demo/dashboard')
+ })
+
+ it('should handle unknown icon types gracefully', async () => {
+ const manifestWithUnknownIcon: HubDemo.Manifest = {
+ routes: [
+ {
+ path: '/unknown',
+ label: 'Unknown',
+ icon: 'unknown-icon-type',
+ contentPath: 'unknown',
+ },
+ ],
+ }
+
+ setupMockVerification(manifestWithUnknownIcon)
+
+ const { result } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(result.current.navigationItems).not.toBeNull()
+ })
+
+ expect(result.current.navigationItems![0].icon).toBeUndefined()
+ })
+ })
+
+ describe('memoization', () => {
+ it('should not regenerate routes when manifest and basePath are unchanged', async () => {
+ setupMockVerification(MOCK_ROUTES_MANIFEST)
+
+ const { result, rerender } = renderHook(() => useHubDemo('/hub'))
+
+ await waitFor(() => {
+ expect(result.current.routes).not.toBeNull()
+ })
+
+ const firstRoutes = result.current.routes
+ const firstNavItems = result.current.navigationItems
+
+ rerender()
+
+ expect(result.current.routes).toBe(firstRoutes)
+ expect(result.current.navigationItems).toBe(firstNavItems)
+ })
+ })
+})
diff --git a/webui/src/pages/hub-demo/use-hub-demo.tsx b/webui/src/pages/hub-demo/use-hub-demo.tsx
new file mode 100644
index 000000000..b23109c17
--- /dev/null
+++ b/webui/src/pages/hub-demo/use-hub-demo.tsx
@@ -0,0 +1,95 @@
+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 'utils/workers/scriptVerification'
+
+const ROUTES_MANIFEST_URL = 'https://traefik.github.io/hub-ui-demo-app/config/routes.json'
+
+const HUB_DEMO_NAV_ICONS: Record = {
+ dashboard: ,
+ gateway: ,
+ api: ,
+ portal: ,
+}
+
+const useHubDemoRoutesManifest = (): HubDemo.Manifest | null => {
+ const [manifest, setManifest] = useState(null)
+
+ useEffect(() => {
+ const fetchManifest = async () => {
+ try {
+ const { verified, scriptContent } = await verifySignature(
+ ROUTES_MANIFEST_URL,
+ `${ROUTES_MANIFEST_URL}.sig`,
+ PUBLIC_KEY,
+ )
+
+ if (!verified || !scriptContent) {
+ setManifest(null)
+ return
+ }
+
+ const textDecoder = new TextDecoder()
+ const jsonString = textDecoder.decode(scriptContent)
+ const data: HubDemo.Manifest = JSON.parse(jsonString)
+ setManifest(data)
+ } catch (error) {
+ console.error('Failed to load hub demo manifest:', error)
+ setManifest(null)
+ }
+ }
+
+ fetchManifest()
+ }, [])
+
+ return manifest
+}
+
+export const useHubDemo = (basePath: string) => {
+ const manifest = useHubDemoRoutesManifest()
+
+ const routes = useMemo(() => {
+ if (!manifest) {
+ return null
+ }
+
+ const routeObjects: RouteObject[] = []
+
+ manifest.routes.forEach((route: HubDemo.Route) => {
+ routeObjects.push({
+ path: `${basePath}${route.path}`,
+ element: ,
+ })
+
+ if (route.dynamicSegments) {
+ route.dynamicSegments.forEach((segment) => {
+ routeObjects.push({
+ path: `${basePath}${route.path}/${segment}`,
+ element: ,
+ })
+ })
+ }
+ })
+
+ return routeObjects
+ }, [basePath, manifest])
+
+ const navigationItems = useMemo(() => {
+ if (!manifest) {
+ return null
+ }
+
+ return manifest.routes.map((route) => ({
+ path: `${basePath}${route.path}`,
+ label: route.label,
+ icon: HUB_DEMO_NAV_ICONS[route.icon],
+ activeMatches: route.activeMatches?.map((r) => `${basePath}${r}`),
+ }))
+ }, [basePath, manifest])
+
+ return { routes, navigationItems }
+}
diff --git a/webui/src/pages/tcp/TcpMiddleware.spec.tsx b/webui/src/pages/tcp/TcpMiddleware.spec.tsx
index b783e0958..73e69e01c 100644
--- a/webui/src/pages/tcp/TcpMiddleware.spec.tsx
+++ b/webui/src/pages/tcp/TcpMiddleware.spec.tsx
@@ -7,6 +7,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/middlewares/mock-middleware', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -14,6 +15,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/middlewares/mock-middleware', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -21,6 +23,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/middlewares/mock-middleware', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -53,6 +56,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/tcp/middlewares/middleware-simple', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
@@ -103,6 +107,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/tcp/middlewares/middleware-complex', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
diff --git a/webui/src/pages/tcp/TcpMiddleware.tsx b/webui/src/pages/tcp/TcpMiddleware.tsx
index f3637a46d..d85cf22ec 100644
--- a/webui/src/pages/tcp/TcpMiddleware.tsx
+++ b/webui/src/pages/tcp/TcpMiddleware.tsx
@@ -1,11 +1,11 @@
import { Card, Box, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
+import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import { DetailSectionSkeleton } from 'components/resources/DetailSections'
import { RenderMiddleware } from 'components/resources/MiddlewarePanel'
import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection'
import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
import { NotFound } from 'pages/NotFound'
import breakpoints from 'utils/breakpoints'
@@ -27,23 +27,29 @@ type TcpMiddlewareRenderProps = {
export const TcpMiddlewareRender = ({ data, error, name }: TcpMiddlewareRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Middleware right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
-
+ >
)
}
@@ -52,7 +58,10 @@ export const TcpMiddlewareRender = ({ data, error, name }: TcpMiddlewareRenderPr
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
{data.name}
@@ -60,7 +69,7 @@ export const TcpMiddlewareRender = ({ data, error, name }: TcpMiddlewareRenderPr
-
+ >
)
}
diff --git a/webui/src/pages/tcp/TcpMiddlewares.spec.tsx b/webui/src/pages/tcp/TcpMiddlewares.spec.tsx
index 3da9cfc5b..12e95f92f 100644
--- a/webui/src/pages/tcp/TcpMiddlewares.spec.tsx
+++ b/webui/src/pages/tcp/TcpMiddlewares.spec.tsx
@@ -29,10 +29,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/tcp/middlewares',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('TCP Middlewares page')).toBeInTheDocument()
+ expect(getByTestId('/tcp/middlewares page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(2)
@@ -58,6 +61,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/tcp/middlewares', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/tcp/TcpMiddlewares.tsx b/webui/src/pages/tcp/TcpMiddlewares.tsx
index c736d4c47..25bca597b 100644
--- a/webui/src/pages/tcp/TcpMiddlewares.tsx
+++ b/webui/src/pages/tcp/TcpMiddlewares.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -13,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
import { parseMiddlewareType } from 'libs/parsers'
export const makeRowRender = (): RenderRowType => {
@@ -79,9 +79,7 @@ export const TcpMiddlewaresRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -109,7 +107,10 @@ export const TcpMiddlewares = () => {
)
return (
-
+ <>
+
+ TCP Middlewares - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/pages/tcp/TcpRouter.spec.tsx b/webui/src/pages/tcp/TcpRouter.spec.tsx
index bd5fe7059..c1f5bb6a4 100644
--- a/webui/src/pages/tcp/TcpRouter.spec.tsx
+++ b/webui/src/pages/tcp/TcpRouter.spec.tsx
@@ -7,6 +7,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/routers/mock-router', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -14,6 +15,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/routers/mock-router', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -21,6 +23,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/routers/mock-router', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -66,6 +69,7 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/tcp/routers/tcp-all@docker', withPage: true },
)
const routerStructure = getByTestId('router-structure')
diff --git a/webui/src/pages/tcp/TcpRouter.tsx b/webui/src/pages/tcp/TcpRouter.tsx
index 1bdac707c..92f9b47b2 100644
--- a/webui/src/pages/tcp/TcpRouter.tsx
+++ b/webui/src/pages/tcp/TcpRouter.tsx
@@ -1,4 +1,5 @@
import { Flex, styled, Text } from '@traefiklabs/faency'
+import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import { CardListSection, DetailSectionSkeleton } from 'components/resources/DetailSections'
@@ -6,7 +7,6 @@ import MiddlewarePanel from 'components/resources/MiddlewarePanel'
import RouterPanel from 'components/resources/RouterPanel'
import TlsPanel from 'components/resources/TlsPanel'
import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
import { RouterStructure } from 'pages/http/HttpRouter'
import { NotFound } from 'pages/NotFound'
@@ -37,17 +37,23 @@ type TcpRouterRenderProps = {
export const TcpRouterRender = ({ data, error, name }: TcpRouterRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Router right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
@@ -57,7 +63,7 @@ export const TcpRouterRender = ({ data, error, name }: TcpRouterRenderProps) =>
-
+ >
)
}
@@ -66,10 +72,13 @@ export const TcpRouterRender = ({ data, error, name }: TcpRouterRenderProps) =>
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
-
+ >
)
}
diff --git a/webui/src/pages/tcp/TcpRouters.spec.tsx b/webui/src/pages/tcp/TcpRouters.spec.tsx
index 60acab68e..fac801fb6 100644
--- a/webui/src/pages/tcp/TcpRouters.spec.tsx
+++ b/webui/src/pages/tcp/TcpRouters.spec.tsx
@@ -39,10 +39,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/tcp/routers',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('TCP Routers page')).toBeInTheDocument()
+ expect(getByTestId('/tcp/routers page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3)
@@ -76,6 +79,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/tcp/routers', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/tcp/TcpRouters.tsx b/webui/src/pages/tcp/TcpRouters.tsx
index de8319e79..8a8f638ac 100644
--- a/webui/src/pages/tcp/TcpRouters.tsx
+++ b/webui/src/pages/tcp/TcpRouters.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import { FiShield } from 'react-icons/fi'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -15,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const TcpRoutersRenderRow = (row) => (
@@ -96,9 +96,7 @@ export const TcpRoutersRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -126,7 +124,10 @@ export const TcpRouters = () => {
)
return (
-
+ <>
+
+ TCP Routers - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/pages/tcp/TcpService.spec.tsx b/webui/src/pages/tcp/TcpService.spec.tsx
index 4860bd9e8..faa8ac0f5 100644
--- a/webui/src/pages/tcp/TcpService.spec.tsx
+++ b/webui/src/pages/tcp/TcpService.spec.tsx
@@ -7,6 +7,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/services/mock-service', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -14,6 +15,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/services/mock-service', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -21,6 +23,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/tcp/services/mock-service', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -33,8 +36,18 @@ describe('', () => {
address: 'http://10.0.1.12:80',
},
],
- passHostHeader: true,
terminationDelay: 10,
+ healthCheck: {
+ interval: '30s',
+ timeout: '10s',
+ port: 8080,
+ unhealthyInterval: '1m',
+ send: 'PING',
+ expect: 'PONG',
+ },
+ },
+ serverStatus: {
+ 'http://10.0.1.12:80': 'UP',
},
status: 'enabled',
usedBy: ['router-test1@docker'],
@@ -59,25 +72,38 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/tcp/services/mock-service', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
const titleTags = headings.filter((h1) => h1.innerHTML === 'service-test1')
expect(titleTags.length).toBe(1)
- const serviceDetails = getByTestId('service-details')
+ const serviceDetails = getByTestId('tcp-service-details')
expect(serviceDetails.innerHTML).toContain('Type')
expect(serviceDetails.innerHTML).toContain('loadbalancer')
expect(serviceDetails.innerHTML).toContain('Provider')
expect(serviceDetails.querySelector('svg[data-testid="docker"]')).toBeTruthy()
expect(serviceDetails.innerHTML).toContain('Status')
expect(serviceDetails.innerHTML).toContain('Success')
- expect(serviceDetails.innerHTML).toContain('Pass Host Header')
- expect(serviceDetails.innerHTML).toContain('True')
expect(serviceDetails.innerHTML).toContain('Termination Delay')
expect(serviceDetails.innerHTML).toContain('10 ms')
- const serversList = getByTestId('servers-list')
+ const healthCheck = getByTestId('tcp-health-check')
+ expect(healthCheck.innerHTML).toContain('Interval')
+ expect(healthCheck.innerHTML).toContain('30s')
+ expect(healthCheck.innerHTML).toContain('Timeout')
+ expect(healthCheck.innerHTML).toContain('10s')
+ expect(healthCheck.innerHTML).toContain('Port')
+ expect(healthCheck.innerHTML).toContain('8080')
+ expect(healthCheck.innerHTML).toContain('Unhealthy Interval')
+ expect(healthCheck.innerHTML).toContain('1m')
+ expect(healthCheck.innerHTML).toContain('Send')
+ expect(healthCheck.innerHTML).toContain('PING')
+ expect(healthCheck.innerHTML).toContain('Expect')
+ expect(healthCheck.innerHTML).toContain('PONG')
+
+ const serversList = getByTestId('tcp-servers-list')
expect(serversList.childNodes.length).toBe(1)
expect(serversList.innerHTML).toContain('http://10.0.1.12:80')
@@ -128,9 +154,10 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/tcp/services/mock-service', withPage: true },
)
- const serversList = getByTestId('servers-list')
+ const serversList = getByTestId('tcp-servers-list')
expect(serversList.childNodes.length).toBe(1)
expect(serversList.innerHTML).toContain('http://10.0.1.12:81')
@@ -154,10 +181,69 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/tcp/services/mock-service', withPage: true },
)
expect(() => {
getByTestId('routers-table')
}).toThrow('Unable to find an element by: [data-testid="routers-table"]')
})
+
+ it('should render weighted services', async () => {
+ const mockData = {
+ weighted: {
+ services: [
+ {
+ name: 'service1@docker',
+ weight: 80,
+ },
+ {
+ name: 'service2@kubernetes',
+ weight: 20,
+ },
+ ],
+ },
+ status: 'enabled',
+ usedBy: ['router-test1@docker'],
+ name: 'weighted-service-test',
+ provider: 'docker',
+ type: 'weighted',
+ routers: [
+ {
+ entryPoints: ['tcp'],
+ service: 'weighted-service-test',
+ rule: 'HostSNI(`*`)',
+ status: 'enabled',
+ using: ['tcp'],
+ name: 'router-test1@docker',
+ provider: 'docker',
+ },
+ ],
+ }
+
+ const { container, getByTestId } = renderWithProviders(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ,
+ )
+
+ const headings = Array.from(container.getElementsByTagName('h1'))
+ const titleTags = headings.filter((h1) => h1.innerHTML === 'weighted-service-test')
+ expect(titleTags.length).toBe(1)
+
+ const serviceDetails = getByTestId('tcp-service-details')
+ expect(serviceDetails.innerHTML).toContain('Type')
+ expect(serviceDetails.innerHTML).toContain('weighted')
+ expect(serviceDetails.innerHTML).toContain('Provider')
+ expect(serviceDetails.querySelector('svg[data-testid="docker"]')).toBeTruthy()
+ expect(serviceDetails.innerHTML).toContain('Status')
+ expect(serviceDetails.innerHTML).toContain('Success')
+
+ const weightedServices = getByTestId('tcp-weighted-services')
+ expect(weightedServices.childNodes.length).toBe(2)
+ expect(weightedServices.innerHTML).toContain('service1@docker')
+ expect(weightedServices.innerHTML).toContain('80')
+ expect(weightedServices.innerHTML).toContain('service2@kubernetes')
+ expect(weightedServices.innerHTML).toContain('20')
+ expect(weightedServices.querySelector('svg[data-testid="docker"]')).toBeTruthy()
+ })
})
diff --git a/webui/src/pages/tcp/TcpService.tsx b/webui/src/pages/tcp/TcpService.tsx
index 6c1d262b5..c36106156 100644
--- a/webui/src/pages/tcp/TcpService.tsx
+++ b/webui/src/pages/tcp/TcpService.tsx
@@ -1,19 +1,234 @@
-import { Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
+import { Box, Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
+import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
+import { FiGlobe, FiInfo, FiShield } from 'react-icons/fi'
import { useParams } from 'react-router-dom'
-import { DetailSectionSkeleton } from 'components/resources/DetailSections'
+import ProviderIcon from 'components/icons/providers'
+import {
+ DetailSection,
+ DetailSectionSkeleton,
+ ItemBlock,
+ ItemTitle,
+ LayoutTwoCols,
+ ProviderName,
+} from 'components/resources/DetailSections'
+import { ResourceStatus } from 'components/resources/ResourceStatus'
import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection'
-import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
-import { ServicePanels } from 'pages/http/HttpService'
+import Tooltip from 'components/Tooltip'
+import { ResourceDetailDataType, ServiceDetailType, useResourceDetail } from 'hooks/use-resource-detail'
import { NotFound } from 'pages/NotFound'
+type TcpDetailProps = {
+ data: ServiceDetailType
+}
+
const SpacedColumns = styled(Flex, {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(360px, 1fr))',
gridGap: '16px',
})
+const ServicesGrid = styled(Box, {
+ display: 'grid',
+ gridTemplateColumns: '2fr 1fr 1fr',
+ alignItems: 'center',
+ padding: '$3 $5',
+ borderBottom: '1px solid $tableRowBorder',
+})
+
+const ServersGrid = styled(Box, {
+ display: 'grid',
+ alignItems: 'center',
+ padding: '$3 $5',
+ borderBottom: '1px solid $tableRowBorder',
+})
+
+const GridTitle = styled(Text, {
+ fontSize: '14px',
+ fontWeight: 700,
+ color: 'hsl(0, 0%, 56%)',
+})
+
+type TcpServer = {
+ address: string
+}
+
+type ServerStatus = {
+ [server: string]: string
+}
+
+type TcpHealthCheck = {
+ port?: number
+ send?: string
+ expect?: string
+ interval?: string
+ unhealthyInterval?: string
+ timeout?: string
+}
+
+function getTcpServerStatusList(data: ServiceDetailType): ServerStatus {
+ const serversList: ServerStatus = {}
+
+ data.loadBalancer?.servers?.forEach((server: any) => {
+ // TCP servers should have address, but handle both url and address for compatibility
+ const serverKey = (server as TcpServer).address || (server as any).url
+ if (serverKey) {
+ serversList[serverKey] = 'DOWN'
+ }
+ })
+
+ if (data.serverStatus) {
+ Object.entries(data.serverStatus).forEach(([server, status]) => {
+ serversList[server] = status
+ })
+ }
+
+ return serversList
+}
+
+export const TcpServicePanels = ({ data }: TcpDetailProps) => {
+ const serversList = getTcpServerStatusList(data)
+ const getProviderFromName = (serviceName: string): string => {
+ const [, provider] = serviceName.split('@')
+ return provider || data.provider
+ }
+ const providerName = useMemo(() => {
+ return data.provider
+ }, [data.provider])
+
+ return (
+
+ } title="Service Details">
+
+ {data.type && (
+
+ {data.type}
+
+ )}
+ {data.provider && (
+
+
+ {providerName}
+
+ )}
+
+ {data.status && (
+
+
+
+ )}
+ {data.loadBalancer && (
+ <>
+ {data.loadBalancer.terminationDelay && (
+
+ {`${data.loadBalancer.terminationDelay} ms`}
+
+ )}
+ >
+ )}
+
+ {data.loadBalancer?.healthCheck && (
+ } title="Health Check">
+
+ {(() => {
+ const tcpHealthCheck = data.loadBalancer.healthCheck as unknown as TcpHealthCheck
+ return (
+ <>
+
+ {tcpHealthCheck.interval && (
+
+ {tcpHealthCheck.interval}
+
+ )}
+ {tcpHealthCheck.timeout && (
+
+ {tcpHealthCheck.timeout}
+
+ )}
+
+
+ {tcpHealthCheck.port && (
+
+ {tcpHealthCheck.port}
+
+ )}
+ {tcpHealthCheck.unhealthyInterval && (
+
+ {tcpHealthCheck.unhealthyInterval}
+
+ )}
+
+
+ {tcpHealthCheck.send && (
+
+
+ {tcpHealthCheck.send}
+
+
+ )}
+ {tcpHealthCheck.expect && (
+
+
+ {tcpHealthCheck.expect}
+
+
+ )}
+
+ >
+ )
+ })()}
+
+
+ )}
+ {!!data?.weighted?.services?.length && (
+ } title="Services" noPadding>
+ <>
+
+ Name
+ Weight
+ Provider
+
+
+ {data.weighted.services.map((service) => (
+
+ {service.name}
+ {service.weight}
+
+
+
+
+ ))}
+
+ >
+
+ )}
+ {Object.keys(serversList).length > 0 && (
+ } title="Servers" noPadding>
+ <>
+
+ Status
+ Address
+
+
+ {Object.entries(serversList).map(([server, status]) => (
+
+
+
+
+ {server}
+
+
+
+ ))}
+
+ >
+
+ )}
+
+ )
+}
+
type TcpServiceRenderProps = {
data?: ResourceDetailDataType
error?: Error
@@ -23,24 +238,31 @@ type TcpServiceRenderProps = {
export const TcpServiceRender = ({ data, error, name }: TcpServiceRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Service right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
+
-
+ >
)
}
@@ -49,11 +271,14 @@ export const TcpServiceRender = ({ data, error, name }: TcpServiceRenderProps) =
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
{data.name}
-
+
-
+ >
)
}
diff --git a/webui/src/pages/tcp/TcpServices.spec.tsx b/webui/src/pages/tcp/TcpServices.spec.tsx
index cd838bee4..336a26a54 100644
--- a/webui/src/pages/tcp/TcpServices.spec.tsx
+++ b/webui/src/pages/tcp/TcpServices.spec.tsx
@@ -36,10 +36,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/tcp/services',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('TCP Services page')).toBeInTheDocument()
+ expect(getByTestId('/tcp/services page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3)
@@ -73,6 +76,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/tcp/services', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/tcp/TcpServices.tsx b/webui/src/pages/tcp/TcpServices.tsx
index a56027b61..13df8792b 100644
--- a/webui/src/pages/tcp/TcpServices.tsx
+++ b/webui/src/pages/tcp/TcpServices.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -13,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const TcpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const TcpServicesRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -108,7 +106,10 @@ export const TcpServices = () => {
)
return (
-
+ <>
+
+ TCP Services - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/pages/udp/UdpRouter.spec.tsx b/webui/src/pages/udp/UdpRouter.spec.tsx
index 65aa5567d..2404b70a3 100644
--- a/webui/src/pages/udp/UdpRouter.spec.tsx
+++ b/webui/src/pages/udp/UdpRouter.spec.tsx
@@ -7,6 +7,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/udp/routers/mock-router', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -14,6 +15,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/udp/routers/mock-router', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -21,6 +23,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/udp/routers/mock-router', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -51,6 +54,7 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/udp/routers/udp-all@docker', withPage: true },
)
const routerStructure = getByTestId('router-structure')
diff --git a/webui/src/pages/udp/UdpRouter.tsx b/webui/src/pages/udp/UdpRouter.tsx
index d1aebf1e8..a41bdb0df 100644
--- a/webui/src/pages/udp/UdpRouter.tsx
+++ b/webui/src/pages/udp/UdpRouter.tsx
@@ -1,10 +1,10 @@
import { Flex, styled, Text } from '@traefiklabs/faency'
+import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import { CardListSection, DetailSectionSkeleton } from 'components/resources/DetailSections'
import RouterPanel from 'components/resources/RouterPanel'
import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
import { RouterStructure } from 'pages/http/HttpRouter'
import { NotFound } from 'pages/NotFound'
@@ -33,17 +33,23 @@ type UdpRouterRenderProps = {
export const UdpRouterRender = ({ data, error, name }: UdpRouterRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Router right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
@@ -53,7 +59,7 @@ export const UdpRouterRender = ({ data, error, name }: UdpRouterRenderProps) =>
-
+ >
)
}
@@ -62,10 +68,13 @@ export const UdpRouterRender = ({ data, error, name }: UdpRouterRenderProps) =>
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
-
+ >
)
}
diff --git a/webui/src/pages/udp/UdpRouters.spec.tsx b/webui/src/pages/udp/UdpRouters.spec.tsx
index 8045cf8b7..658b8843b 100644
--- a/webui/src/pages/udp/UdpRouters.spec.tsx
+++ b/webui/src/pages/udp/UdpRouters.spec.tsx
@@ -39,10 +39,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/udp/routers',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('UDP Routers page')).toBeInTheDocument()
+ expect(getByTestId('/udp/routers page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3)
@@ -76,6 +79,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/udp/routers', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/udp/UdpRouters.tsx b/webui/src/pages/udp/UdpRouters.tsx
index 56c6d8414..b468630ce 100644
--- a/webui/src/pages/udp/UdpRouters.tsx
+++ b/webui/src/pages/udp/UdpRouters.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -14,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const UdpRoutersRenderRow = (row) => (
@@ -81,9 +81,7 @@ export const UdpRoutersRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -111,7 +109,10 @@ export const UdpRouters = () => {
)
return (
-
+ <>
+
+ UDP Routers - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/pages/udp/UdpService.spec.tsx b/webui/src/pages/udp/UdpService.spec.tsx
index 71114f560..b6150c8ee 100644
--- a/webui/src/pages/udp/UdpService.spec.tsx
+++ b/webui/src/pages/udp/UdpService.spec.tsx
@@ -7,6 +7,7 @@ describe('', () => {
it('should render the error message', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/udp/services/mock-service', withPage: true },
)
expect(getByTestId('error-text')).toBeInTheDocument()
})
@@ -14,6 +15,7 @@ describe('', () => {
it('should render the skeleton', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/udp/services/mock-service', withPage: true },
)
expect(getByTestId('skeleton')).toBeInTheDocument()
})
@@ -21,6 +23,7 @@ describe('', () => {
it('should render the not found page', () => {
const { getByTestId } = renderWithProviders(
,
+ { route: '/udp/services/mock-service', withPage: true },
)
expect(getByTestId('Not found page')).toBeInTheDocument()
})
@@ -59,6 +62,7 @@ describe('', () => {
const { container, getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/udp/services/mock-service', withPage: true },
)
const headings = Array.from(container.getElementsByTagName('h1'))
@@ -128,6 +132,7 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/udp/services/mock-service', withPage: true },
)
const serversList = getByTestId('servers-list')
@@ -154,6 +159,7 @@ describe('', () => {
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
+ { route: '/udp/services/mock-service', withPage: true },
)
expect(() => {
diff --git a/webui/src/pages/udp/UdpService.tsx b/webui/src/pages/udp/UdpService.tsx
index 9727a1663..132e63197 100644
--- a/webui/src/pages/udp/UdpService.tsx
+++ b/webui/src/pages/udp/UdpService.tsx
@@ -1,10 +1,10 @@
import { Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
+import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import { DetailSectionSkeleton } from 'components/resources/DetailSections'
import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection'
import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail'
-import Page from 'layout/Page'
import { ServicePanels } from 'pages/http/HttpService'
import { NotFound } from 'pages/NotFound'
@@ -23,24 +23,30 @@ type UdpServiceRenderProps = {
export const UdpServiceRender = ({ data, error, name }: UdpServiceRenderProps) => {
if (error) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
Sorry, we could not fetch detail information for this Service right now. Please, try again later.
-
+ >
)
}
if (!data) {
return (
-
+ <>
+
+ {name} - Traefik Proxy
+
-
+ >
)
}
@@ -49,11 +55,14 @@ export const UdpServiceRender = ({ data, error, name }: UdpServiceRenderProps) =
}
return (
-
+ <>
+
+ {data.name} - Traefik Proxy
+
{data.name}
-
+ >
)
}
diff --git a/webui/src/pages/udp/UdpServices.spec.tsx b/webui/src/pages/udp/UdpServices.spec.tsx
index 8bb51ef0d..e28d1dba3 100644
--- a/webui/src/pages/udp/UdpServices.spec.tsx
+++ b/webui/src/pages/udp/UdpServices.spec.tsx
@@ -36,10 +36,13 @@ describe('', () => {
.spyOn(useFetchWithPagination, 'default')
.mockImplementation(() => useFetchWithPaginationMock({ pages }))
- const { container, getByTestId } = renderWithProviders()
+ const { container, getByTestId } = renderWithProviders(, {
+ route: '/udp/services',
+ withPage: true,
+ })
expect(mock).toHaveBeenCalled()
- expect(getByTestId('UDP Services page')).toBeInTheDocument()
+ expect(getByTestId('/udp/services page')).toBeInTheDocument()
const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1]
expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3)
@@ -73,6 +76,7 @@ describe('', () => {
pageCount={1}
pages={[]}
/>,
+ { route: '/udp/services', withPage: true },
)
expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]')
const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2]
diff --git a/webui/src/pages/udp/UdpServices.tsx b/webui/src/pages/udp/UdpServices.tsx
index b02c2fdcf..76abc3d02 100644
--- a/webui/src/pages/udp/UdpServices.tsx
+++ b/webui/src/pages/udp/UdpServices.tsx
@@ -1,5 +1,6 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency'
import { useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useSearchParams } from 'react-router-dom'
@@ -13,8 +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 Page from 'layout/Page'
+import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const UdpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const UdpServicesRender = ({
{(isEmpty || !!error) && (
-
-
-
+
)}
@@ -108,7 +106,10 @@ export const UdpServices = () => {
)
return (
-
+ <>
+
+ UDP Services - Traefik Proxy
+
{
pageCount={pageCount}
pages={pages}
/>
-
+ >
)
}
diff --git a/webui/src/types/global.d.ts b/webui/src/types/global.d.ts
index e1503ed42..4b024488c 100644
--- a/webui/src/types/global.d.ts
+++ b/webui/src/types/global.d.ts
@@ -5,5 +5,6 @@ interface Window {
declare namespace JSX {
interface IntrinsicElements {
'hub-button-app': React.DetailedHTMLProps, HTMLElement>
+ 'hub-ui-demo-app': { key: string; path: string; theme: 'dark' | 'light'; baseurl: string; containercss: string }
}
}
diff --git a/webui/src/utils/test.tsx b/webui/src/utils/test.tsx
index 2c79760cf..995908e9f 100644
--- a/webui/src/utils/test.tsx
+++ b/webui/src/utils/test.tsx
@@ -1,10 +1,11 @@
import { cleanup, render } from '@testing-library/react'
import { FaencyProvider } from '@traefiklabs/faency'
import { HelmetProvider } from 'react-helmet-async'
-import { BrowserRouter } from 'react-router-dom'
+import { MemoryRouter } from 'react-router-dom'
import { SWRConfig } from 'swr'
import { afterEach } from 'vitest'
+import Page from '../layout/Page'
import fetch from '../libs/fetch'
afterEach(() => {
@@ -25,7 +26,7 @@ export { default as userEvent } from '@testing-library/user-event'
// override render export
export { customRender as render } // eslint-disable-line import/export
-export function renderWithProviders(ui: React.ReactElement) {
+export function renderWithProviders(ui: React.ReactElement, { route = '/', withPage = false } = {}) {
return customRender(ui, {
wrapper: ({ children }) => (
@@ -36,7 +37,7 @@ export function renderWithProviders(ui: React.ReactElement) {
fetcher: fetch,
}}
>
- {children}
+ {withPage ? {children} : children}
diff --git a/webui/src/utils/workers/scriptVerification.integration.spec.ts b/webui/src/utils/workers/scriptVerification.integration.spec.ts
new file mode 100644
index 000000000..189632c64
--- /dev/null
+++ b/webui/src/utils/workers/scriptVerification.integration.spec.ts
@@ -0,0 +1,156 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+
+import verifySignature from './scriptVerification'
+
+describe('Script Signature Verification - Integration Tests', () => {
+ let fetchMock: ReturnType
+
+ const SCRIPT_URL = 'https://example.com/script.js'
+ const SIGNATURE_URL = 'https://example.com/script.js.sig'
+ const TEST_PUBLIC_KEY = 'MCowBQYDK2VwAyEAWH71OHphISjNK3mizCR/BawiDxc6IXT1vFHpBcxSIA0='
+ const VALID_SCRIPT = "console.log('Hello from verified script!');"
+ const VALID_SIGNATURE_HEX =
+ '04c90fcd35caaf3cf4582a2767345f8cd9f6519e1ce79ebaeedbe0d5f671d762d1aa8ec258831557e2de0e47f224883f84eb5a0f22ec18eb7b8c48de3096d000'
+ const CORRUPTED_SCRIPT = "console.log('Malicious code injected!');"
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ fetchMock = vi.fn()
+ globalThis.fetch = fetchMock
+ })
+
+ it('should verify a valid script with correct signature through real worker', async () => {
+ fetchMock.mockImplementation((url: string) => {
+ if (url === SCRIPT_URL) {
+ return Promise.resolve(
+ new Response(VALID_SCRIPT, {
+ status: 200,
+ headers: { 'Content-Type': 'application/javascript' },
+ }),
+ )
+ }
+ if (url === SIGNATURE_URL) {
+ return Promise.resolve(
+ new Response(VALID_SIGNATURE_HEX, {
+ status: 200,
+ headers: { 'Content-Type': 'text/plain' },
+ }),
+ )
+ }
+ return Promise.reject(new Error('Unexpected URL'))
+ })
+
+ const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY)
+
+ expect(fetchMock).toHaveBeenCalledWith(SCRIPT_URL)
+ expect(fetchMock).toHaveBeenCalledWith(SIGNATURE_URL)
+ expect(result.verified).toBe(true)
+ expect(result.scriptContent).toBeDefined()
+ }, 15000)
+
+ it('should reject a corrupted script with mismatched signature', async () => {
+ fetchMock.mockImplementation((url: string) => {
+ if (url === SCRIPT_URL) {
+ return Promise.resolve(
+ new Response(CORRUPTED_SCRIPT, {
+ status: 200,
+ headers: { 'Content-Type': 'application/javascript' },
+ }),
+ )
+ }
+ if (url === SIGNATURE_URL) {
+ return Promise.resolve(
+ new Response(VALID_SIGNATURE_HEX, {
+ status: 200,
+ headers: { 'Content-Type': 'text/plain' },
+ }),
+ )
+ }
+ return Promise.reject(new Error('Unexpected URL'))
+ })
+
+ const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY)
+
+ expect(fetchMock).toHaveBeenCalledWith(SCRIPT_URL)
+ expect(fetchMock).toHaveBeenCalledWith(SIGNATURE_URL)
+ expect(result.verified).toBe(false)
+ expect(result.scriptContent).toBeUndefined()
+ }, 15000)
+
+ it('should reject script with invalid signature format', async () => {
+ fetchMock.mockImplementation((url: string) => {
+ if (url === SCRIPT_URL) {
+ return Promise.resolve(
+ new Response(VALID_SCRIPT, {
+ status: 200,
+ headers: { 'Content-Type': 'application/javascript' },
+ }),
+ )
+ }
+ if (url === SIGNATURE_URL) {
+ return Promise.resolve(
+ new Response('not-a-valid-signature', {
+ status: 200,
+ headers: { 'Content-Type': 'text/plain' },
+ }),
+ )
+ }
+ return Promise.reject(new Error('Unexpected URL'))
+ })
+
+ const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY)
+
+ expect(result.verified).toBe(false)
+ expect(result.scriptContent).toBeUndefined()
+ }, 15000)
+
+ it('should reject script with wrong public key', async () => {
+ const WRONG_PUBLIC_KEY = 'MCowBQYDK2VwAyEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
+
+ fetchMock.mockImplementation((url: string) => {
+ if (url === SCRIPT_URL) {
+ return Promise.resolve(
+ new Response(VALID_SCRIPT, {
+ status: 200,
+ headers: { 'Content-Type': 'application/javascript' },
+ }),
+ )
+ }
+ if (url === SIGNATURE_URL) {
+ return Promise.resolve(
+ new Response(VALID_SIGNATURE_HEX, {
+ status: 200,
+ headers: { 'Content-Type': 'text/plain' },
+ }),
+ )
+ }
+ return Promise.reject(new Error('Unexpected URL'))
+ })
+
+ const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, WRONG_PUBLIC_KEY)
+
+ expect(result.verified).toBe(false)
+ expect(result.scriptContent).toBeUndefined()
+ }, 15000)
+
+ it('should handle network failures when fetching script', async () => {
+ fetchMock.mockImplementation(() =>
+ Promise.resolve(
+ new Response(null, {
+ status: 404,
+ statusText: 'Not Found',
+ }),
+ ),
+ )
+
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
+
+ const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY)
+
+ expect(result.verified).toBe(false)
+ expect(result.scriptContent).toBeUndefined()
+ expect(consoleErrorSpy).toHaveBeenCalled()
+
+ consoleErrorSpy.mockRestore()
+ }, 15000)
+})
diff --git a/webui/src/utils/workers/scriptVerification.spec.ts b/webui/src/utils/workers/scriptVerification.spec.ts
new file mode 100644
index 000000000..0d6c0108e
--- /dev/null
+++ b/webui/src/utils/workers/scriptVerification.spec.ts
@@ -0,0 +1,117 @@
+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
+ postMessage = vi.fn()
+ terminate = vi.fn()
+
+ simulateMessage(data: unknown) {
+ if (this.onmessage) {
+ this.onmessage(new MessageEvent('message', { data }))
+ }
+ }
+
+ simulateError(error: Error) {
+ if (this.onerror) {
+ this.onerror(new ErrorEvent('error', { error, message: error.message }))
+ }
+ }
+}
+
+describe('verifySignature', () => {
+ let mockWorkerInstance: MockWorker
+ let originalWorker: typeof Worker
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+
+ originalWorker = globalThis.Worker
+
+ mockWorkerInstance = new MockWorker()
+
+ globalThis.Worker = class extends EventTarget {
+ constructor() {
+ super()
+ return mockWorkerInstance as any
+ }
+ } as any
+ })
+
+ afterEach(() => {
+ globalThis.Worker = originalWorker
+ vi.restoreAllMocks()
+ })
+
+ it('should return true when verification succeeds', async () => {
+ 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: SCRIPT_PATH,
+ signatureUrl: `${SCRIPT_PATH}.sig`,
+ requestId: expect.any(String),
+ }),
+ )
+
+ const mockScriptContent = new ArrayBuffer(100)
+ mockWorkerInstance.simulateMessage({
+ success: true,
+ verified: true,
+ error: null,
+ scriptContent: mockScriptContent,
+ })
+
+ const result = await promise
+
+ expect(result).toEqual({ verified: true, scriptContent: mockScriptContent })
+ expect(mockWorkerInstance.terminate).toHaveBeenCalled()
+ })
+
+ it('should return false when verification fails', async () => {
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
+
+ const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
+
+ await new Promise((resolve) => setTimeout(resolve, 0))
+
+ mockWorkerInstance.simulateMessage({
+ success: false,
+ verified: false,
+ error: 'Signature verification failed',
+ })
+
+ const result = await promise
+
+ expect(result).toEqual({ verified: false })
+ expect(mockWorkerInstance.terminate).toHaveBeenCalled()
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Worker verification failed:', 'Signature verification failed')
+
+ consoleErrorSpy.mockRestore()
+ })
+
+ it('should return false when worker throws an error', async () => {
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
+
+ const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
+
+ await new Promise((resolve) => setTimeout(resolve, 0))
+
+ const error = new Error('Worker crashed')
+ mockWorkerInstance.simulateError(error)
+
+ const result = await promise
+
+ expect(result).toEqual({ verified: false })
+ expect(mockWorkerInstance.terminate).toHaveBeenCalled()
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Worker error:', expect.any(ErrorEvent))
+
+ consoleErrorSpy.mockRestore()
+ })
+})
diff --git a/webui/src/utils/workers/scriptVerification.ts b/webui/src/utils/workers/scriptVerification.ts
new file mode 100644
index 000000000..fb3ecedd5
--- /dev/null
+++ b/webui/src/utils/workers/scriptVerification.ts
@@ -0,0 +1,55 @@
+export interface VerificationResult {
+ verified: boolean
+ scriptContent?: ArrayBuffer
+}
+
+async function verifySignature(
+ contentPath: string,
+ signaturePath: string,
+ publicKey: string,
+): Promise {
+ return new Promise((resolve) => {
+ const requestId = Math.random().toString(36).substring(2)
+ const worker = new Worker(new URL('./scriptVerificationWorker.ts', import.meta.url), { type: 'module' })
+
+ const timeout = setTimeout(() => {
+ worker.terminate()
+ console.error('Script verification timeout')
+ resolve({ verified: false })
+ }, 30000)
+
+ worker.onmessage = (event) => {
+ clearTimeout(timeout)
+ worker.terminate()
+
+ const { success, verified, error, scriptContent } = event.data
+
+ if (!success) {
+ console.error('Worker verification failed:', error)
+ resolve({ verified: false })
+ return
+ }
+
+ resolve({
+ verified: verified === true,
+ scriptContent: verified ? scriptContent : undefined,
+ })
+ }
+
+ worker.onerror = (error) => {
+ clearTimeout(timeout)
+ worker.terminate()
+ console.error('Worker error:', error)
+ resolve({ verified: false })
+ }
+
+ worker.postMessage({
+ requestId,
+ scriptUrl: contentPath,
+ signatureUrl: signaturePath,
+ publicKey,
+ })
+ })
+}
+
+export default verifySignature
diff --git a/webui/src/utils/workers/scriptVerificationWorker.ts b/webui/src/utils/workers/scriptVerificationWorker.ts
new file mode 100644
index 000000000..902dce094
--- /dev/null
+++ b/webui/src/utils/workers/scriptVerificationWorker.ts
@@ -0,0 +1,189 @@
+// Script verification worker
+// Runs in isolated context for secure verification
+
+import { verify } from '@noble/ed25519'
+import * as ed25519 from '@noble/ed25519'
+import { sha512 } from '@noble/hashes/sha2.js'
+
+// Set up SHA-512 for @noble/ed25519 v3.x
+ed25519.hashes.sha512 = sha512
+ed25519.hashes.sha512Async = (m) => Promise.resolve(sha512(m))
+
+function base64ToArrayBuffer(base64: string): ArrayBuffer {
+ try {
+ // @ts-expect-error - fromBase64 is not yet in all TypeScript lib definitions
+ const bytes = Uint8Array.fromBase64(base64)
+ return bytes.buffer
+ } catch {
+ // Fallback for browsers without Uint8Array.fromBase64()
+ const binaryString = atob(base64)
+ const bytes = new Uint8Array(binaryString.length)
+ for (let i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i)
+ }
+ return bytes.buffer
+ }
+}
+
+function extractEd25519PublicKey(spkiBytes: Uint8Array): Uint8Array {
+ if (spkiBytes.length !== 44) {
+ throw new Error('Invalid SPKI length for Ed25519')
+ }
+ return spkiBytes.slice(-32)
+}
+
+async function importPublicKeyWebCrypto(publicKey: string): Promise {
+ const publicKeyBuffer = base64ToArrayBuffer(publicKey)
+
+ return await crypto.subtle.importKey(
+ 'spki',
+ publicKeyBuffer,
+ {
+ name: 'Ed25519',
+ },
+ false,
+ ['verify'],
+ )
+}
+
+async function verifyWithWebCrypto(
+ publicKey: string,
+ scriptBuffer: ArrayBuffer,
+ signatureBuffer: ArrayBuffer,
+): Promise {
+ try {
+ const cryptoPublicKey = await importPublicKeyWebCrypto(publicKey)
+
+ return await crypto.subtle.verify('Ed25519', cryptoPublicKey, signatureBuffer, scriptBuffer)
+ } catch (error) {
+ console.log('Web Crypto verification failed:', error instanceof Error ? error.message : 'Unknown error')
+ return false
+ }
+}
+
+function parseSignature(signatureBuffer: ArrayBuffer): Uint8Array {
+ const signatureBytes = new Uint8Array(signatureBuffer)
+
+ // If already 64 bytes, assume it's raw binary
+ if (signatureBytes.length === 64) {
+ return signatureBytes
+ }
+
+ // Try to parse as text (base64 or hex)
+ const signatureText = new TextDecoder().decode(signatureBytes).trim()
+
+ // base64 decoding
+ try {
+ const base64Decoded = new Uint8Array(base64ToArrayBuffer(signatureText))
+ if (base64Decoded.length === 64) {
+ return base64Decoded
+ }
+ } catch (e) {
+ console.error(e)
+ }
+
+ // hex decoding
+ if (signatureText.length === 128 && /^[0-9a-fA-F]+$/.test(signatureText)) {
+ try {
+ // @ts-expect-error - fromHex is not yet in all TypeScript lib definitions
+ return Uint8Array.fromHex(signatureText)
+ } catch {
+ // Fallback for browsers without Uint8Array.fromHex()
+ const hexDecoded = new Uint8Array(64)
+ for (let i = 0; i < 64; i++) {
+ hexDecoded[i] = parseInt(signatureText.slice(i * 2, i * 2 + 2), 16)
+ }
+ return hexDecoded
+ }
+ }
+
+ throw new Error(`Unable to parse signature format.`)
+}
+
+async function verifyWithNoble(
+ publicKey: string,
+ scriptBuffer: ArrayBuffer,
+ signatureBuffer: ArrayBuffer,
+): Promise {
+ try {
+ const publicKeySpki = new Uint8Array(base64ToArrayBuffer(publicKey))
+ const publicKeyRaw = extractEd25519PublicKey(publicKeySpki)
+
+ const scriptBytes = new Uint8Array(scriptBuffer)
+ const signatureBytes = parseSignature(signatureBuffer)
+
+ return verify(signatureBytes, scriptBytes, publicKeyRaw)
+ } catch (error) {
+ console.log('Noble verification failed:', error instanceof Error ? error.message : 'Unknown error')
+ return false
+ }
+}
+
+self.onmessage = async function (event) {
+ const { requestId, scriptUrl, signatureUrl, publicKey } = event.data
+
+ try {
+ const [scriptResponse, signatureResponse] = await Promise.all([fetch(scriptUrl), fetch(signatureUrl)])
+
+ if (!scriptResponse.ok || !signatureResponse.ok) {
+ self.postMessage({
+ requestId,
+ success: false,
+ verified: false,
+ error: `Failed to fetch files. Script: ${scriptResponse.status} ${scriptResponse.statusText}, Signature: ${signatureResponse.status} ${signatureResponse.statusText}`,
+ })
+ return
+ }
+
+ const [scriptBuffer, signatureBuffer] = await Promise.all([
+ scriptResponse.arrayBuffer(),
+ signatureResponse.arrayBuffer(),
+ ])
+
+ // Try Web Crypto API first, fallback to Noble if it fails
+ let verified = await verifyWithWebCrypto(publicKey, scriptBuffer, signatureBuffer)
+
+ if (!verified) {
+ verified = await verifyWithNoble(publicKey, scriptBuffer, signatureBuffer)
+ }
+
+ // If verified, include script content to avoid re-downloading
+ let scriptContent: ArrayBuffer | undefined
+ if (verified) {
+ scriptContent = scriptBuffer
+ }
+
+ // Send message with transferable ArrayBuffer for efficiency
+ const message = {
+ requestId,
+ success: true,
+ verified,
+ scriptSize: scriptBuffer.byteLength,
+ signatureSize: signatureBuffer.byteLength,
+ scriptContent,
+ }
+
+ if (scriptContent) {
+ self.postMessage(message, { transfer: [scriptContent] })
+ } else {
+ self.postMessage(message)
+ }
+ } catch (error) {
+ console.error('[Worker] Verification error:', error)
+ self.postMessage({
+ requestId,
+ success: false,
+ verified: false,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ })
+ }
+}
+
+self.onerror = function (error) {
+ console.error('[Worker] Worker error:', error)
+ self.postMessage({
+ success: false,
+ verified: false,
+ error,
+ })
+}
diff --git a/webui/test/setup.ts b/webui/test/setup.ts
index 7c53d16b8..fe734b9b7 100644
--- a/webui/test/setup.ts
+++ b/webui/test/setup.ts
@@ -1,5 +1,6 @@
import '@testing-library/jest-dom'
import 'vitest-canvas-mock'
+import '@vitest/web-worker'
import * as matchers from 'jest-extended'
import { expect } from 'vitest'
@@ -12,6 +13,7 @@ export class IntersectionObserver {
root = null
rootMargin = ''
thresholds = []
+ scrollMargin = ''
disconnect() {
return null
@@ -43,10 +45,10 @@ class ResizeObserver {
}
beforeAll(() => {
- global.IntersectionObserver = IntersectionObserver
+ globalThis.IntersectionObserver = IntersectionObserver
window.IntersectionObserver = IntersectionObserver
- global.ResizeObserver = ResizeObserver
+ globalThis.ResizeObserver = ResizeObserver
window.ResizeObserver = ResizeObserver
Object.defineProperty(window, 'matchMedia', {
diff --git a/webui/yarn.lock b/webui/yarn.lock
index 3934fc003..efb7038fd 100644
--- a/webui/yarn.lock
+++ b/webui/yarn.lock
@@ -94,6 +94,29 @@ __metadata:
languageName: node
linkType: hard
+"@babel/core@npm:^7.23.9":
+ version: 7.28.5
+ resolution: "@babel/core@npm:7.28.5"
+ dependencies:
+ "@babel/code-frame": "npm:^7.27.1"
+ "@babel/generator": "npm:^7.28.5"
+ "@babel/helper-compilation-targets": "npm:^7.27.2"
+ "@babel/helper-module-transforms": "npm:^7.28.3"
+ "@babel/helpers": "npm:^7.28.4"
+ "@babel/parser": "npm:^7.28.5"
+ "@babel/template": "npm:^7.27.2"
+ "@babel/traverse": "npm:^7.28.5"
+ "@babel/types": "npm:^7.28.5"
+ "@jridgewell/remapping": "npm:^2.3.5"
+ convert-source-map: "npm:^2.0.0"
+ debug: "npm:^4.1.0"
+ gensync: "npm:^1.0.0-beta.2"
+ json5: "npm:^2.2.3"
+ semver: "npm:^6.3.1"
+ checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72
+ languageName: node
+ linkType: hard
+
"@babel/core@npm:^7.28.0":
version: 7.28.0
resolution: "@babel/core@npm:7.28.0"
@@ -143,6 +166,19 @@ __metadata:
languageName: node
linkType: hard
+"@babel/generator@npm:^7.28.5":
+ version: 7.28.5
+ resolution: "@babel/generator@npm:7.28.5"
+ dependencies:
+ "@babel/parser": "npm:^7.28.5"
+ "@babel/types": "npm:^7.28.5"
+ "@jridgewell/gen-mapping": "npm:^0.3.12"
+ "@jridgewell/trace-mapping": "npm:^0.3.28"
+ jsesc: "npm:^3.0.2"
+ checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752
+ languageName: node
+ linkType: hard
+
"@babel/helper-annotate-as-pure@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/helper-annotate-as-pure@npm:7.25.9"
@@ -240,7 +276,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.25.9":
+"@babel/helper-module-imports@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/helper-module-imports@npm:7.25.9"
dependencies:
@@ -286,6 +322,19 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-module-transforms@npm:^7.28.3":
+ version: 7.28.3
+ resolution: "@babel/helper-module-transforms@npm:7.28.3"
+ dependencies:
+ "@babel/helper-module-imports": "npm:^7.27.1"
+ "@babel/helper-validator-identifier": "npm:^7.27.1"
+ "@babel/traverse": "npm:^7.28.3"
+ peerDependencies:
+ "@babel/core": ^7.0.0
+ checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb
+ languageName: node
+ linkType: hard
+
"@babel/helper-optimise-call-expression@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/helper-optimise-call-expression@npm:7.25.9"
@@ -373,6 +422,13 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-validator-identifier@npm:^7.28.5":
+ version: 7.28.5
+ resolution: "@babel/helper-validator-identifier@npm:7.28.5"
+ checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847
+ languageName: node
+ linkType: hard
+
"@babel/helper-validator-option@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/helper-validator-option@npm:7.25.9"
@@ -418,6 +474,16 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helpers@npm:^7.28.4":
+ version: 7.28.4
+ resolution: "@babel/helpers@npm:7.28.4"
+ dependencies:
+ "@babel/template": "npm:^7.27.2"
+ "@babel/types": "npm:^7.28.4"
+ checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44
+ languageName: node
+ linkType: hard
+
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0":
version: 7.27.0
resolution: "@babel/parser@npm:7.27.0"
@@ -440,6 +506,17 @@ __metadata:
languageName: node
linkType: hard
+"@babel/parser@npm:^7.28.5":
+ version: 7.28.5
+ resolution: "@babel/parser@npm:7.28.5"
+ dependencies:
+ "@babel/types": "npm:^7.28.5"
+ bin:
+ parser: ./bin/babel-parser.js
+ checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef
+ languageName: node
+ linkType: hard
+
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9"
@@ -541,6 +618,17 @@ __metadata:
languageName: node
linkType: hard
+"@babel/plugin-syntax-typescript@npm:^7.23.3":
+ version: 7.27.1
+ resolution: "@babel/plugin-syntax-typescript@npm:7.27.1"
+ dependencies:
+ "@babel/helper-plugin-utils": "npm:^7.27.1"
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+ checksum: 10c0/11589b4c89c66ef02d57bf56c6246267851ec0c361f58929327dc3e070b0dab644be625bbe7fb4c4df30c3634bfdfe31244e1f517be397d2def1487dbbe3c37d
+ languageName: node
+ linkType: hard
+
"@babel/plugin-syntax-typescript@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/plugin-syntax-typescript@npm:7.25.9"
@@ -1443,6 +1531,21 @@ __metadata:
languageName: node
linkType: hard
+"@babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5":
+ version: 7.28.5
+ resolution: "@babel/traverse@npm:7.28.5"
+ dependencies:
+ "@babel/code-frame": "npm:^7.27.1"
+ "@babel/generator": "npm:^7.28.5"
+ "@babel/helper-globals": "npm:^7.28.0"
+ "@babel/parser": "npm:^7.28.5"
+ "@babel/template": "npm:^7.27.2"
+ "@babel/types": "npm:^7.28.5"
+ debug: "npm:^4.3.1"
+ checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f
+ languageName: node
+ linkType: hard
+
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.4.4":
version: 7.27.0
resolution: "@babel/types@npm:7.27.0"
@@ -1463,6 +1566,16 @@ __metadata:
languageName: node
linkType: hard
+"@babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5":
+ version: 7.28.5
+ resolution: "@babel/types@npm:7.28.5"
+ dependencies:
+ "@babel/helper-string-parser": "npm:^7.27.1"
+ "@babel/helper-validator-identifier": "npm:^7.28.5"
+ checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a
+ languageName: node
+ linkType: hard
+
"@bcoe/v8-coverage@npm:^1.0.2":
version: 1.0.2
resolution: "@bcoe/v8-coverage@npm:1.0.2"
@@ -1590,6 +1703,13 @@ __metadata:
languageName: node
linkType: hard
+"@emotion/hash@npm:^0.9.0":
+ version: 0.9.2
+ resolution: "@emotion/hash@npm:0.9.2"
+ checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2
+ languageName: node
+ linkType: hard
+
"@esbuild/aix-ppc64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/aix-ppc64@npm:0.21.5"
@@ -1597,6 +1717,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/aix-ppc64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/aix-ppc64@npm:0.25.12"
+ conditions: os=aix & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/aix-ppc64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/aix-ppc64@npm:0.25.2"
@@ -1618,6 +1745,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/android-arm64@npm:0.25.12"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/android-arm64@npm:0.25.2"
@@ -1639,6 +1773,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/android-arm@npm:0.25.12"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/android-arm@npm:0.25.2"
@@ -1660,6 +1801,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/android-x64@npm:0.25.12"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/android-x64@npm:0.25.2"
@@ -1681,6 +1829,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/darwin-arm64@npm:0.25.12"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-arm64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/darwin-arm64@npm:0.25.2"
@@ -1702,6 +1857,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/darwin-x64@npm:0.25.12"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/darwin-x64@npm:0.25.2"
@@ -1723,6 +1885,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/freebsd-arm64@npm:0.25.12"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-arm64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/freebsd-arm64@npm:0.25.2"
@@ -1744,6 +1913,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/freebsd-x64@npm:0.25.12"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/freebsd-x64@npm:0.25.2"
@@ -1765,6 +1941,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-arm64@npm:0.25.12"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-arm64@npm:0.25.2"
@@ -1786,6 +1969,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-arm@npm:0.25.12"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-arm@npm:0.25.2"
@@ -1807,6 +1997,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ia32@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-ia32@npm:0.25.12"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ia32@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-ia32@npm:0.25.2"
@@ -1828,6 +2025,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-loong64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-loong64@npm:0.25.12"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-loong64@npm:0.25.2"
@@ -1849,6 +2053,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-mips64el@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-mips64el@npm:0.25.12"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-mips64el@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-mips64el@npm:0.25.2"
@@ -1870,6 +2081,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ppc64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-ppc64@npm:0.25.12"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ppc64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-ppc64@npm:0.25.2"
@@ -1891,6 +2109,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-riscv64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-riscv64@npm:0.25.12"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-riscv64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-riscv64@npm:0.25.2"
@@ -1912,6 +2137,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-s390x@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-s390x@npm:0.25.12"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-s390x@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-s390x@npm:0.25.2"
@@ -1933,6 +2165,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-x64@npm:0.25.12"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/linux-x64@npm:0.25.2"
@@ -1947,6 +2186,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/netbsd-arm64@npm:0.25.12"
+ conditions: os=netbsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-arm64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/netbsd-arm64@npm:0.25.2"
@@ -1968,6 +2214,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/netbsd-x64@npm:0.25.12"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/netbsd-x64@npm:0.25.2"
@@ -1982,6 +2235,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/openbsd-arm64@npm:0.25.12"
+ conditions: os=openbsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-arm64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/openbsd-arm64@npm:0.25.2"
@@ -2003,6 +2263,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/openbsd-x64@npm:0.25.12"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/openbsd-x64@npm:0.25.2"
@@ -2017,6 +2284,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openharmony-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/openharmony-arm64@npm:0.25.12"
+ conditions: os=openharmony & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/openharmony-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/openharmony-arm64@npm:0.25.8"
@@ -2031,6 +2305,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/sunos-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/sunos-x64@npm:0.25.12"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/sunos-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/sunos-x64@npm:0.25.2"
@@ -2052,6 +2333,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/win32-arm64@npm:0.25.12"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-arm64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/win32-arm64@npm:0.25.2"
@@ -2073,6 +2361,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-ia32@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/win32-ia32@npm:0.25.12"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-ia32@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/win32-ia32@npm:0.25.2"
@@ -2094,6 +2389,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/win32-x64@npm:0.25.12"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-x64@npm:0.25.2":
version: 0.25.2
resolution: "@esbuild/win32-x64@npm:0.25.2"
@@ -2357,6 +2659,22 @@ __metadata:
languageName: node
linkType: hard
+"@isaacs/balanced-match@npm:^4.0.1":
+ version: 4.0.1
+ resolution: "@isaacs/balanced-match@npm:4.0.1"
+ checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420
+ languageName: node
+ linkType: hard
+
+"@isaacs/brace-expansion@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "@isaacs/brace-expansion@npm:5.0.0"
+ dependencies:
+ "@isaacs/balanced-match": "npm:^4.0.1"
+ checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977
+ languageName: node
+ linkType: hard
+
"@isaacs/cliui@npm:^8.0.2":
version: 8.0.2
resolution: "@isaacs/cliui@npm:8.0.2"
@@ -2464,6 +2782,16 @@ __metadata:
languageName: node
linkType: hard
+"@jridgewell/remapping@npm:^2.3.5":
+ version: 2.3.5
+ resolution: "@jridgewell/remapping@npm:2.3.5"
+ dependencies:
+ "@jridgewell/gen-mapping": "npm:^0.3.5"
+ "@jridgewell/trace-mapping": "npm:^0.3.24"
+ checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194
+ languageName: node
+ linkType: hard
+
"@jridgewell/resolve-uri@npm:^3.1.0":
version: 3.1.2
resolution: "@jridgewell/resolve-uri@npm:3.1.2"
@@ -2485,6 +2813,13 @@ __metadata:
languageName: node
linkType: hard
+"@jridgewell/sourcemap-codec@npm:^1.5.5":
+ version: 1.5.5
+ resolution: "@jridgewell/sourcemap-codec@npm:1.5.5"
+ checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0
+ languageName: node
+ linkType: hard
+
"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25":
version: 0.3.25
resolution: "@jridgewell/trace-mapping@npm:0.3.25"
@@ -2536,6 +2871,60 @@ __metadata:
languageName: node
linkType: hard
+"@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.16.0"
+ "@microsoft/tsdoc-config": "npm:~0.18.0"
+ "@rushstack/node-core-library": "npm:5.18.0"
+ checksum: 10c0/e6d9c54a457c66dec53765522f411c8ca5d2bb8c51559b9f952fdd7bb88b10efe035a8dbdf8b672644c136d75a65f3446b3b18938fa50c0a766f83111cbda5eb
+ languageName: node
+ linkType: hard
+
+"@microsoft/api-extractor@npm:^7.50.1":
+ version: 7.55.0
+ resolution: "@microsoft/api-extractor@npm:7.55.0"
+ dependencies:
+ "@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"
+ "@rushstack/ts-command-line": "npm:5.1.3"
+ diff: "npm:~8.0.2"
+ lodash: "npm:~4.17.15"
+ minimatch: "npm:10.0.3"
+ resolve: "npm:~1.22.1"
+ semver: "npm:~7.5.4"
+ source-map: "npm:~0.6.1"
+ typescript: "npm:5.8.2"
+ bin:
+ api-extractor: bin/api-extractor
+ checksum: 10c0/3211981b7aaf6ca7a36fe33dc9cab5014dc753c0c75d09ace46bef07db947a433ca9daecc843ea13b29fe7527ea3d357c7bd5051fedf10ae3b3db31d2a5de71f
+ languageName: node
+ linkType: hard
+
+"@microsoft/tsdoc-config@npm:~0.18.0":
+ version: 0.18.0
+ resolution: "@microsoft/tsdoc-config@npm:0.18.0"
+ dependencies:
+ "@microsoft/tsdoc": "npm:0.16.0"
+ ajv: "npm:~8.12.0"
+ jju: "npm:~1.4.0"
+ resolve: "npm:~1.22.2"
+ checksum: 10c0/6e2c3bfde3e5fa4c0360127c86fe016dcf1b09d0091d767c06ce916284d3f6aeea3617a33b855c5bb2615ab0f2840eeebd4c7f4a1f879f951828d213bf306cfd
+ languageName: node
+ linkType: hard
+
+"@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
+
"@mswjs/interceptors@npm:^0.37.0":
version: 0.37.6
resolution: "@mswjs/interceptors@npm:0.37.6"
@@ -2561,6 +2950,20 @@ __metadata:
languageName: node
linkType: hard
+"@noble/ed25519@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "@noble/ed25519@npm:3.0.0"
+ checksum: 10c0/6355aed04523d063d3e9a9952926af4a0eaa722e08133f558d44963a3048bf6dae02cae9032d42238d1fcb2a93ef27658f2baf4cf07939457717a4337a89dc26
+ languageName: node
+ linkType: hard
+
+"@noble/hashes@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "@noble/hashes@npm:2.0.1"
+ checksum: 10c0/e81769ce21c3b1c80141a3b99bd001f17edea09879aa936692ae39525477386d696101cd573928a304806efb2b9fa751e1dd83241c67d0c84d30091e85c79bdb
+ languageName: node
+ linkType: hard
+
"@nodelib/fs.scandir@npm:2.1.5":
version: 2.1.5
resolution: "@nodelib/fs.scandir@npm:2.1.5"
@@ -3406,7 +3809,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-icons@npm:^1.1.1":
+"@radix-ui/react-icons@npm:^1.3.2":
version: 1.3.2
resolution: "@radix-ui/react-icons@npm:1.3.2"
peerDependencies:
@@ -4050,46 +4453,6 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/plugin-babel@npm:^5.3.1":
- version: 5.3.1
- resolution: "@rollup/plugin-babel@npm:5.3.1"
- dependencies:
- "@babel/helper-module-imports": "npm:^7.10.4"
- "@rollup/pluginutils": "npm:^3.1.0"
- peerDependencies:
- "@babel/core": ^7.0.0
- "@types/babel__core": ^7.1.9
- rollup: ^1.20.0||^2.0.0
- peerDependenciesMeta:
- "@types/babel__core":
- optional: true
- checksum: 10c0/2766134dd5567c0d4fd6909d1f511ce9bf3bd9d727e1bc5ffdd6097a3606faca324107ae8e0961839ee4dbb45e5e579ae601efe472fc0a271259aea79920cafa
- languageName: node
- linkType: hard
-
-"@rollup/pluginutils@npm:^3.1.0":
- version: 3.1.0
- resolution: "@rollup/pluginutils@npm:3.1.0"
- dependencies:
- "@types/estree": "npm:0.0.39"
- estree-walker: "npm:^1.0.1"
- picomatch: "npm:^2.2.2"
- peerDependencies:
- rollup: ^1.20.0||^2.0.0
- checksum: 10c0/7151753160d15ba2b259461a6c25b3932150994ea52dba8fd3144f634c7647c2e56733d986e2c15de67c4d96a9ee7d6278efa6d2e626a7169898fd64adc0f90c
- languageName: node
- linkType: hard
-
-"@rollup/pluginutils@npm:^4.1.2":
- version: 4.2.1
- resolution: "@rollup/pluginutils@npm:4.2.1"
- dependencies:
- estree-walker: "npm:^2.0.1"
- picomatch: "npm:^2.2.2"
- checksum: 10c0/3ee56b2c8f1ed8dfd0a92631da1af3a2dfdd0321948f089b3752b4de1b54dc5076701eadd0e5fc18bd191b77af594ac1db6279e83951238ba16bf8a414c64c48
- languageName: node
- linkType: hard
-
"@rollup/pluginutils@npm:^5.0.2":
version: 5.1.4
resolution: "@rollup/pluginutils@npm:5.1.4"
@@ -4106,6 +4469,22 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/pluginutils@npm:^5.1.4":
+ version: 5.3.0
+ resolution: "@rollup/pluginutils@npm:5.3.0"
+ dependencies:
+ "@types/estree": "npm:^1.0.0"
+ estree-walker: "npm:^2.0.2"
+ picomatch: "npm:^4.0.2"
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ checksum: 10c0/001834bf62d7cf5bac424d2617c113f7f7d3b2bf3c1778cbcccb72cdc957b68989f8e7747c782c2b911f1dde8257f56f8ac1e779e29e74e638e3f1e2cac2bcd0
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-android-arm-eabi@npm:4.39.0":
version: 4.39.0
resolution: "@rollup/rollup-android-arm-eabi@npm:4.39.0"
@@ -4113,9 +4492,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-android-arm-eabi@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.1"
+"@rollup/rollup-android-arm-eabi@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-android-arm-eabi@npm:4.52.5"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
@@ -4127,9 +4506,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-android-arm64@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-android-arm64@npm:4.46.1"
+"@rollup/rollup-android-arm64@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-android-arm64@npm:4.52.5"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
@@ -4141,9 +4520,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-darwin-arm64@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-darwin-arm64@npm:4.46.1"
+"@rollup/rollup-darwin-arm64@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-darwin-arm64@npm:4.52.5"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
@@ -4155,9 +4534,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-darwin-x64@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-darwin-x64@npm:4.46.1"
+"@rollup/rollup-darwin-x64@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-darwin-x64@npm:4.52.5"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
@@ -4169,9 +4548,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-freebsd-arm64@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.1"
+"@rollup/rollup-freebsd-arm64@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-freebsd-arm64@npm:4.52.5"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
@@ -4183,9 +4562,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-freebsd-x64@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-freebsd-x64@npm:4.46.1"
+"@rollup/rollup-freebsd-x64@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-freebsd-x64@npm:4.52.5"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
@@ -4197,9 +4576,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.1"
+"@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5"
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
@@ -4211,9 +4590,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm-musleabihf@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.1"
+"@rollup/rollup-linux-arm-musleabihf@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.52.5"
conditions: os=linux & cpu=arm & libc=musl
languageName: node
linkType: hard
@@ -4225,9 +4604,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm64-gnu@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.1"
+"@rollup/rollup-linux-arm64-gnu@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.52.5"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
@@ -4239,13 +4618,20 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm64-musl@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.1"
+"@rollup/rollup-linux-arm64-musl@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-arm64-musl@npm:4.52.5"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
+"@rollup/rollup-linux-loong64-gnu@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.52.5"
+ conditions: os=linux & cpu=loong64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-loongarch64-gnu@npm:4.39.0":
version: 4.39.0
resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.39.0"
@@ -4253,13 +4639,6 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.1"
- conditions: os=linux & cpu=loong64 & libc=glibc
- languageName: node
- linkType: hard
-
"@rollup/rollup-linux-powerpc64le-gnu@npm:4.39.0":
version: 4.39.0
resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.39.0"
@@ -4267,9 +4646,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-ppc64-gnu@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.1"
+"@rollup/rollup-linux-ppc64-gnu@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.52.5"
conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node
linkType: hard
@@ -4281,9 +4660,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-riscv64-gnu@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.1"
+"@rollup/rollup-linux-riscv64-gnu@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.52.5"
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
@@ -4295,9 +4674,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-riscv64-musl@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.1"
+"@rollup/rollup-linux-riscv64-musl@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.52.5"
conditions: os=linux & cpu=riscv64 & libc=musl
languageName: node
linkType: hard
@@ -4309,9 +4688,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-s390x-gnu@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.1"
+"@rollup/rollup-linux-s390x-gnu@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.52.5"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
@@ -4323,9 +4702,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-x64-gnu@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.1"
+"@rollup/rollup-linux-x64-gnu@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-x64-gnu@npm:4.52.5"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
@@ -4337,13 +4716,20 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-linux-x64-musl@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.1"
+"@rollup/rollup-linux-x64-musl@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-linux-x64-musl@npm:4.52.5"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
+"@rollup/rollup-openharmony-arm64@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-openharmony-arm64@npm:4.52.5"
+ conditions: os=openharmony & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-win32-arm64-msvc@npm:4.39.0":
version: 4.39.0
resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.39.0"
@@ -4351,9 +4737,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-win32-arm64-msvc@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.1"
+"@rollup/rollup-win32-arm64-msvc@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.52.5"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
@@ -4365,13 +4751,20 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-win32-ia32-msvc@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.1"
+"@rollup/rollup-win32-ia32-msvc@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.52.5"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
+"@rollup/rollup-win32-x64-gnu@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-win32-x64-gnu@npm:4.52.5"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-win32-x64-msvc@npm:4.39.0":
version: 4.39.0
resolution: "@rollup/rollup-win32-x64-msvc@npm:4.39.0"
@@ -4379,9 +4772,9 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-win32-x64-msvc@npm:4.46.1":
- version: 4.46.1
- resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.1"
+"@rollup/rollup-win32-x64-msvc@npm:4.52.5":
+ version: 4.52.5
+ resolution: "@rollup/rollup-win32-x64-msvc@npm:4.52.5"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -4393,6 +4786,77 @@ __metadata:
languageName: node
linkType: hard
+"@rushstack/node-core-library@npm:5.18.0":
+ version: 5.18.0
+ resolution: "@rushstack/node-core-library@npm:5.18.0"
+ dependencies:
+ ajv: "npm:~8.13.0"
+ ajv-draft-04: "npm:~1.0.0"
+ ajv-formats: "npm:~3.0.1"
+ fs-extra: "npm:~11.3.0"
+ import-lazy: "npm:~4.0.0"
+ jju: "npm:~1.4.0"
+ resolve: "npm:~1.22.1"
+ semver: "npm:~7.5.4"
+ peerDependencies:
+ "@types/node": "*"
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ checksum: 10c0/dc9744f5359178b9772508f1f7229c7586bf7e74942e6e28e3bcd48d1af97d5a9d4866fe243e3b0003309896b134780837033115291c37c1b8da363bb3e551bf
+ languageName: node
+ linkType: hard
+
+"@rushstack/problem-matcher@npm:0.1.1":
+ version: 0.1.1
+ resolution: "@rushstack/problem-matcher@npm:0.1.1"
+ peerDependencies:
+ "@types/node": "*"
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ checksum: 10c0/c847e721d3536ebb316fdd90b3e4033a7d24ff8c70e38e3eaeaadf167c4d14a7f16377ae4af8097532386bcfa81c15cfec7d2da517542c07882d273d56861d78
+ languageName: node
+ linkType: hard
+
+"@rushstack/rig-package@npm:0.6.0":
+ version: 0.6.0
+ resolution: "@rushstack/rig-package@npm:0.6.0"
+ dependencies:
+ resolve: "npm:~1.22.1"
+ strip-json-comments: "npm:~3.1.1"
+ checksum: 10c0/303c5c010a698343124036414dbeed44b24e67585307ffa6effd052624b0384cc08a12aeb153e8466b7abd6f516900ecf8629600230f0f2c33cd5c0c3dace65e
+ languageName: node
+ linkType: hard
+
+"@rushstack/terminal@npm:0.19.3":
+ version: 0.19.3
+ resolution: "@rushstack/terminal@npm:0.19.3"
+ dependencies:
+ "@rushstack/node-core-library": "npm:5.18.0"
+ "@rushstack/problem-matcher": "npm:0.1.1"
+ supports-color: "npm:~8.1.1"
+ peerDependencies:
+ "@types/node": "*"
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ checksum: 10c0/aae29b9f7968192af2750ec0a0d31dd9d320494838a8f8a312a0773c2dc1420b087a2a6f98c9915d0215da0c9f7a11f58a4954758b62fce569c62f4daa32368d
+ languageName: node
+ linkType: hard
+
+"@rushstack/ts-command-line@npm:5.1.3":
+ version: 5.1.3
+ resolution: "@rushstack/ts-command-line@npm:5.1.3"
+ dependencies:
+ "@rushstack/terminal": "npm:0.19.3"
+ "@types/argparse": "npm:1.0.38"
+ argparse: "npm:~1.0.9"
+ string-argv: "npm:~0.3.1"
+ checksum: 10c0/03263347660eb2fd9e4c88e704f7611f9d16f3f31ce43726165ab3421dff3cfdb51c9e25170f96dd8c0c3681595d6a1665bfec16224718ea20173507ef531240
+ languageName: node
+ linkType: hard
+
"@samverschueren/stream-to-observable@npm:^0.3.0":
version: 0.3.1
resolution: "@samverschueren/stream-to-observable@npm:0.3.1"
@@ -4522,16 +4986,23 @@ __metadata:
languageName: node
linkType: hard
-"@stitches/react@npm:1.2.7":
- version: 1.2.7
- resolution: "@stitches/react@npm:1.2.7"
- peerDependencies:
- react: ">= 16.3.0"
- checksum: 10c0/c5da5e4b862306d2dd0545765c497bf5b972315a801ee7120374c13950db49587fe3f3ef5282be1ce189f9de2acbe3a4d0f8ba40283fe058b8d056674a336e60
+"@standard-schema/spec@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "@standard-schema/spec@npm:1.0.0"
+ checksum: 10c0/a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f
languageName: node
linkType: hard
-"@storybook/addon-actions@npm:8.6.12, @storybook/addon-actions@npm:^8.2.2":
+"@stitches/react@npm:1.2.8":
+ version: 1.2.8
+ resolution: "@stitches/react@npm:1.2.8"
+ peerDependencies:
+ react: ">= 16.3.0"
+ checksum: 10c0/d9c03cd88ac25edc493b23a1359f36aa9a497d13bb7a833204d8228b09b6227c1a8ea22e946312ca605f7819fbdd3c49d5f8a5f14b2801c9a31d36172d7f60ac
+ languageName: node
+ linkType: hard
+
+"@storybook/addon-actions@npm:8.6.12":
version: 8.6.12
resolution: "@storybook/addon-actions@npm:8.6.12"
dependencies:
@@ -4967,9 +5438,9 @@ __metadata:
languageName: node
linkType: hard
-"@traefiklabs/faency@npm:11.1.4":
- version: 11.1.4
- resolution: "@traefiklabs/faency@npm:11.1.4"
+"@traefiklabs/faency@npm:12.0.4":
+ version: 12.0.4
+ resolution: "@traefiklabs/faency@npm:12.0.4"
dependencies:
"@babel/core": "npm:^7.15.4"
"@babel/plugin-transform-react-pure-annotations": "npm:^7.16.7"
@@ -4988,7 +5459,7 @@ __metadata:
"@radix-ui/react-context-menu": "npm:^2.0.1"
"@radix-ui/react-dialog": "npm:^1.0.2"
"@radix-ui/react-dropdown-menu": "npm:^2.0.1"
- "@radix-ui/react-icons": "npm:^1.1.1"
+ "@radix-ui/react-icons": "npm:^1.3.2"
"@radix-ui/react-id": "npm:^1.0.0"
"@radix-ui/react-label": "npm:^2.0.0"
"@radix-ui/react-navigation-menu": "npm:^1.2.0"
@@ -5007,13 +5478,11 @@ __metadata:
"@radix-ui/react-use-layout-effect": "npm:^1.0.0"
"@radix-ui/react-visually-hidden": "npm:^1.0.1"
"@rehookify/datepicker": "npm:^6.6.7"
- "@rollup/plugin-babel": "npm:^5.3.1"
"@semantic-release/commit-analyzer": "npm:^9.0.2"
"@semantic-release/github": "npm:^8.0.2"
"@semantic-release/npm": "npm:^9.0.0"
"@semantic-release/release-notes-generator": "npm:^10.0.3"
- "@stitches/react": "npm:1.2.7"
- "@storybook/addon-actions": "npm:^8.2.2"
+ "@stitches/react": "npm:1.2.8"
"@storybook/addon-docs": "npm:^8.2.5"
"@storybook/addon-essentials": "npm:^8.2.5"
"@storybook/addon-links": "npm:^8.2.2"
@@ -5029,6 +5498,10 @@ __metadata:
"@types/react": "npm:18.2.0"
"@types/react-dom": "npm:18.2.0"
"@types/tinycolor2": "npm:^1.4.3"
+ "@vanilla-extract/css": "npm:^1.17.4"
+ "@vanilla-extract/dynamic": "npm:^2.1.5"
+ "@vanilla-extract/recipes": "npm:^0.5.7"
+ "@vanilla-extract/vite-plugin": "npm:^5.1.1"
"@vitejs/plugin-react": "npm:^4.3.1"
babel-loader: "npm:^8.2.2"
conventional-changelog-conventionalcommits: "npm:^4.6.3"
@@ -5039,22 +5512,21 @@ __metadata:
lodash.merge: "npm:^4.6.2"
np: "npm:^8.0.4"
patch-package: "npm:^8.0.0"
- prettier: "npm:^3.3.3"
+ prettier: "npm:^3.6.2"
react: "npm:18.2.0"
react-dom: "npm:18.2.0"
- rollup: "npm:^2.70.1"
- rollup-plugin-typescript2: "npm:^0.36.0"
semantic-release: "npm:^19.0.2"
storybook: "npm:^8.2.5"
storybook-dark-mode: "npm:^4.0.2"
tinycolor2: "npm:^1.4.2"
- typescript: "npm:5.4.5"
+ typescript: "npm:^5.8.3"
use-debounce: "npm:9.0.2"
- vite: "npm:^5.1.5"
+ vite: "npm:^5.4.19"
+ vite-plugin-dts: "npm:^4.5.4"
peerDependencies:
react: ">=18"
react-dom: ">=18"
- checksum: 10c0/3ef3f665b402a247cda758df9d485b6d573c69e1ebb595e69139a087c88bbd25ad0999b016cd28fa8ed174ec90b347bb5a94661a383f357fd07b6bfd9ce4c287
+ checksum: 10c0/3ad37330aebe01ff674acc8d37799dbce6ac9b9971b6dcdb015be10699ce4386e181a53667bd6e2311dd0ffc5880afacd0b9140d308c63255d2338f1e2a7c08d
languageName: node
linkType: hard
@@ -5067,6 +5539,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/argparse@npm:1.0.38":
+ version: 1.0.38
+ resolution: "@types/argparse@npm:1.0.38"
+ checksum: 10c0/4fc892da5df16923f48180da2d1f4562fa8b0507cf636b24780444fa0a1d7321d4dc0c0ecbee6152968823f5a2ae0d321b4f8c705a489bf1ae1245bdeb0868fd
+ languageName: node
+ linkType: hard
+
"@types/aria-query@npm:^5.0.1":
version: 5.0.4
resolution: "@types/aria-query@npm:5.0.4"
@@ -5157,13 +5636,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/estree@npm:0.0.39":
- version: 0.0.39
- resolution: "@types/estree@npm:0.0.39"
- checksum: 10c0/f0af6c95ac1988c4827964bd9d3b51d24da442e2188943f6dfcb1e1559103d5d024d564b2e9d3f84c53714a02a0a7435c7441138eb63d9af5de4dfc66cdc0d92
- languageName: node
- linkType: hard
-
"@types/estree@npm:1.0.7, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6":
version: 1.0.7
resolution: "@types/estree@npm:1.0.7"
@@ -5780,6 +6252,102 @@ __metadata:
languageName: node
linkType: hard
+"@vanilla-extract/babel-plugin-debug-ids@npm:^1.2.2":
+ version: 1.2.2
+ resolution: "@vanilla-extract/babel-plugin-debug-ids@npm:1.2.2"
+ dependencies:
+ "@babel/core": "npm:^7.23.9"
+ checksum: 10c0/ac9dc2fc01cf737a2d6c9e318e4417acf073eead736afd03fab3b1f1a41553b37a0ded291f88c2cbb09e233a60928f2a493405dd14e0353995de79216c44debf
+ languageName: node
+ linkType: hard
+
+"@vanilla-extract/compiler@npm:^0.3.1":
+ version: 0.3.1
+ resolution: "@vanilla-extract/compiler@npm:0.3.1"
+ dependencies:
+ "@vanilla-extract/css": "npm:^1.17.4"
+ "@vanilla-extract/integration": "npm:^8.0.4"
+ vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0"
+ vite-node: "npm:^3.2.2"
+ checksum: 10c0/2863ffb30a9be1c11f1f1e658fded42a558d55f9163c34a6465021671740dbefdc2491bc2434815bf5c6cd5771d529331b1e0cd0c39c162306341acbfb2be768
+ languageName: node
+ linkType: hard
+
+"@vanilla-extract/css@npm:^1.17.4":
+ version: 1.17.4
+ resolution: "@vanilla-extract/css@npm:1.17.4"
+ dependencies:
+ "@emotion/hash": "npm:^0.9.0"
+ "@vanilla-extract/private": "npm:^1.0.9"
+ css-what: "npm:^6.1.0"
+ cssesc: "npm:^3.0.0"
+ csstype: "npm:^3.0.7"
+ dedent: "npm:^1.5.3"
+ deep-object-diff: "npm:^1.1.9"
+ deepmerge: "npm:^4.2.2"
+ lru-cache: "npm:^10.4.3"
+ media-query-parser: "npm:^2.0.2"
+ modern-ahocorasick: "npm:^1.0.0"
+ picocolors: "npm:^1.0.0"
+ checksum: 10c0/f241aef803db870e2b503ca1b8ed81976e839ab884e4c20594a352de7fe8c693920e05b17b4b4892d86d8c65126bec3184f003afc406505cb419b2b5569c11e1
+ languageName: node
+ linkType: hard
+
+"@vanilla-extract/dynamic@npm:^2.1.5":
+ version: 2.1.5
+ resolution: "@vanilla-extract/dynamic@npm:2.1.5"
+ dependencies:
+ "@vanilla-extract/private": "npm:^1.0.9"
+ checksum: 10c0/6b7f445918972579d8f988ccca54f3c9c0fdaf5f2bdff641e5e87eae3952e523acb3ac7243162317941d7cf71ba2383a2a4eeaa32eb5a1b661e098b27e02961d
+ languageName: node
+ linkType: hard
+
+"@vanilla-extract/integration@npm:^8.0.4":
+ version: 8.0.4
+ resolution: "@vanilla-extract/integration@npm:8.0.4"
+ dependencies:
+ "@babel/core": "npm:^7.23.9"
+ "@babel/plugin-syntax-typescript": "npm:^7.23.3"
+ "@vanilla-extract/babel-plugin-debug-ids": "npm:^1.2.2"
+ "@vanilla-extract/css": "npm:^1.17.4"
+ dedent: "npm:^1.5.3"
+ esbuild: "npm:esbuild@>=0.17.6 <0.26.0"
+ eval: "npm:0.1.8"
+ find-up: "npm:^5.0.0"
+ javascript-stringify: "npm:^2.0.1"
+ mlly: "npm:^1.4.2"
+ checksum: 10c0/3f409bb52d44f8c3bc6cd133d89f72ec87e5985bf1c75534b59712c2260da3b3040ba0fd120c61dbc1d400884aa656a502981cf479ffe584379ede779c472617
+ languageName: node
+ linkType: hard
+
+"@vanilla-extract/private@npm:^1.0.9":
+ version: 1.0.9
+ resolution: "@vanilla-extract/private@npm:1.0.9"
+ checksum: 10c0/6ab0f1a63a8c93c655a161add6f4d413d897498411f013ba3179f56e350e17b2b3cf04fe4e7d2d69aa1dd8dac7aeaf07fb39192192aa7ae445c75451b78aa3c8
+ languageName: node
+ linkType: hard
+
+"@vanilla-extract/recipes@npm:^0.5.7":
+ version: 0.5.7
+ resolution: "@vanilla-extract/recipes@npm:0.5.7"
+ peerDependencies:
+ "@vanilla-extract/css": ^1.0.0
+ checksum: 10c0/18e7a7a12dedd16e43a1974c8ee273bee9080bd70042e91ce244e4be5679c4f626a257d37186bbd3562157d48fec26650a50f7a396439862da527e2223687ac8
+ languageName: node
+ linkType: hard
+
+"@vanilla-extract/vite-plugin@npm:^5.1.1":
+ version: 5.1.1
+ resolution: "@vanilla-extract/vite-plugin@npm:5.1.1"
+ dependencies:
+ "@vanilla-extract/compiler": "npm:^0.3.1"
+ "@vanilla-extract/integration": "npm:^8.0.4"
+ peerDependencies:
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0
+ checksum: 10c0/6cd9ae7505bda6f0789bf4538adef987a24ee41bcaa8356775bbf07aac3665d9d29a15ef1057add9895885e66cf661cfe0ba8340a9bcedba95ceccfcf9c16bf0
+ languageName: node
+ linkType: hard
+
"@vitejs/plugin-react@npm:^4.3.1":
version: 4.3.4
resolution: "@vitejs/plugin-react@npm:4.3.4"
@@ -5838,86 +6406,182 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/expect@npm:3.2.4":
- version: 3.2.4
- resolution: "@vitest/expect@npm:3.2.4"
+"@vitest/expect@npm:4.0.3":
+ version: 4.0.3
+ resolution: "@vitest/expect@npm:4.0.3"
dependencies:
+ "@standard-schema/spec": "npm:^1.0.0"
"@types/chai": "npm:^5.2.2"
- "@vitest/spy": "npm:3.2.4"
- "@vitest/utils": "npm:3.2.4"
- chai: "npm:^5.2.0"
- tinyrainbow: "npm:^2.0.0"
- checksum: 10c0/7586104e3fd31dbe1e6ecaafb9a70131e4197dce2940f727b6a84131eee3decac7b10f9c7c72fa5edbdb68b6f854353bd4c0fa84779e274207fb7379563b10db
+ "@vitest/spy": "npm:4.0.3"
+ "@vitest/utils": "npm:4.0.3"
+ chai: "npm:^6.0.1"
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/36a9ff769387f4475fea273ec3ee39553ed7607bda620eea3a3632cd88a1e8c97173abcce7bc4440c84bf55b8e752edacb519a64f02d7ad5b47df7770cb56883
languageName: node
linkType: hard
-"@vitest/mocker@npm:3.2.4":
- version: 3.2.4
- resolution: "@vitest/mocker@npm:3.2.4"
+"@vitest/mocker@npm:4.0.3":
+ version: 4.0.3
+ resolution: "@vitest/mocker@npm:4.0.3"
dependencies:
- "@vitest/spy": "npm:3.2.4"
+ "@vitest/spy": "npm:4.0.3"
estree-walker: "npm:^3.0.3"
- magic-string: "npm:^0.30.17"
+ magic-string: "npm:^0.30.19"
peerDependencies:
msw: ^2.4.9
- vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ vite: ^6.0.0 || ^7.0.0-0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
- checksum: 10c0/f7a4aea19bbbf8f15905847ee9143b6298b2c110f8b64789224cb0ffdc2e96f9802876aa2ca83f1ec1b6e1ff45e822abb34f0054c24d57b29ab18add06536ccd
+ checksum: 10c0/16c9ef064a55b0fd3e0d7adaddfeef5c41d569d8181c40d8fed75464b43445305b83c017b50c5ea88f49355bef64e32f75cfd35db0682e0a8f5bbcc3acbde264
languageName: node
linkType: hard
-"@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4":
- version: 3.2.4
- resolution: "@vitest/pretty-format@npm:3.2.4"
+"@vitest/pretty-format@npm:4.0.3":
+ version: 4.0.3
+ resolution: "@vitest/pretty-format@npm:4.0.3"
dependencies:
- tinyrainbow: "npm:^2.0.0"
- checksum: 10c0/5ad7d4278e067390d7d633e307fee8103958806a419ca380aec0e33fae71b44a64415f7a9b4bc11635d3c13d4a9186111c581d3cef9c65cc317e68f077456887
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/031080fbcb16b42c511ef7553a0cdabcb78d0bf9a2bb960a03403ff4553ce05d15e13af4abe7f22bd187f369d3bfa1c59714a0b4d9db33313d7583346511f236
languageName: node
linkType: hard
-"@vitest/runner@npm:3.2.4":
- version: 3.2.4
- resolution: "@vitest/runner@npm:3.2.4"
+"@vitest/runner@npm:4.0.3":
+ version: 4.0.3
+ resolution: "@vitest/runner@npm:4.0.3"
dependencies:
- "@vitest/utils": "npm:3.2.4"
+ "@vitest/utils": "npm:4.0.3"
pathe: "npm:^2.0.3"
- strip-literal: "npm:^3.0.0"
- checksum: 10c0/e8be51666c72b3668ae3ea348b0196656a4a5adb836cb5e270720885d9517421815b0d6c98bfdf1795ed02b994b7bfb2b21566ee356a40021f5bf4f6ed4e418a
+ checksum: 10c0/77a4c76890d652115baded6004beb13f5f84e98d0a5175c38b3908f03875d6c02df998430e43b21bf324de9e1499069a54cf773854447607602cab5d9fe8cd60
languageName: node
linkType: hard
-"@vitest/snapshot@npm:3.2.4":
- version: 3.2.4
- resolution: "@vitest/snapshot@npm:3.2.4"
+"@vitest/snapshot@npm:4.0.3":
+ version: 4.0.3
+ resolution: "@vitest/snapshot@npm:4.0.3"
dependencies:
- "@vitest/pretty-format": "npm:3.2.4"
- magic-string: "npm:^0.30.17"
+ "@vitest/pretty-format": "npm:4.0.3"
+ magic-string: "npm:^0.30.19"
pathe: "npm:^2.0.3"
- checksum: 10c0/f8301a3d7d1559fd3d59ed51176dd52e1ed5c2d23aa6d8d6aa18787ef46e295056bc726a021698d8454c16ed825ecba163362f42fa90258bb4a98cfd2c9424fc
+ checksum: 10c0/69e6a3ebdf3852cb949237989aa88c73ead9e6a1debd2227d130e0ec3c33a0bc07ab2d16538a247b8eea0c105f0c9f62419700bbc9230edc99d1df8083bf7b0a
languageName: node
linkType: hard
-"@vitest/spy@npm:3.2.4":
- version: 3.2.4
- resolution: "@vitest/spy@npm:3.2.4"
- dependencies:
- tinyspy: "npm:^4.0.3"
- checksum: 10c0/6ebf0b4697dc238476d6b6a60c76ba9eb1dd8167a307e30f08f64149612fd50227682b876420e4c2e09a76334e73f72e3ebf0e350714dc22474258292e202024
+"@vitest/spy@npm:4.0.3":
+ version: 4.0.3
+ resolution: "@vitest/spy@npm:4.0.3"
+ checksum: 10c0/185fc2621c44c974fe2d89523cc8e20db46b4213fb7f153b63c4091c744b14e1e2c79f6160a3db0aab3e91775c6974214499c643032a02c2261cc68692b4cad8
languageName: node
linkType: hard
-"@vitest/utils@npm:3.2.4":
- version: 3.2.4
- resolution: "@vitest/utils@npm:3.2.4"
+"@vitest/utils@npm:4.0.3":
+ version: 4.0.3
+ resolution: "@vitest/utils@npm:4.0.3"
dependencies:
- "@vitest/pretty-format": "npm:3.2.4"
- loupe: "npm:^3.1.4"
- tinyrainbow: "npm:^2.0.0"
- checksum: 10c0/024a9b8c8bcc12cf40183c246c244b52ecff861c6deb3477cbf487ac8781ad44c68a9c5fd69f8c1361878e55b97c10d99d511f2597f1f7244b5e5101d028ba64
+ "@vitest/pretty-format": "npm:4.0.3"
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/b773bb0133307176762a932bab0480e77c9b378406eb92c82f37a16d214b1d873a0378e4c567c4b05bf4628073bc7fc18b519a7c0bdaf75e18311545ca00c5ba
+ languageName: node
+ linkType: hard
+
+"@vitest/web-worker@npm:^4.0.2":
+ version: 4.0.2
+ resolution: "@vitest/web-worker@npm:4.0.2"
+ dependencies:
+ debug: "npm:^4.4.3"
+ peerDependencies:
+ vitest: 4.0.2
+ checksum: 10c0/d30b32b8c7e4df28fbe099125e62f3fc3d0e48a852b3749cbb47963e33070a4c04b3f55bfaf1f90109eacd6bc4d13b4b8028be984033edda532a8372a5abed86
+ languageName: node
+ linkType: hard
+
+"@volar/language-core@npm:2.4.23, @volar/language-core@npm:~2.4.11":
+ version: 2.4.23
+ resolution: "@volar/language-core@npm:2.4.23"
+ dependencies:
+ "@volar/source-map": "npm:2.4.23"
+ checksum: 10c0/1b8d60c7c0faa29ef5ec46dd2b673227592d0697753767e4df088f7c2d93843828116fe59472bb9d604ba653400be32a538e985730844b1af4f42a7075e62049
+ languageName: node
+ linkType: hard
+
+"@volar/source-map@npm:2.4.23":
+ version: 2.4.23
+ resolution: "@volar/source-map@npm:2.4.23"
+ checksum: 10c0/08af690093b811d0a37bdd8d306755b4e7f1535b67625c26f6fa6eb9ae081e24c55dabc8231ce8856aa1b731a5ac137b3f0449b34c093923c3545afdbe462c7a
+ languageName: node
+ linkType: hard
+
+"@volar/typescript@npm:^2.4.11":
+ version: 2.4.23
+ resolution: "@volar/typescript@npm:2.4.23"
+ dependencies:
+ "@volar/language-core": "npm:2.4.23"
+ path-browserify: "npm:^1.0.1"
+ vscode-uri: "npm:^3.0.8"
+ checksum: 10c0/dbb449b66e627a75f8f6df98b3210c32edff62747a12d1e6237a6dc2a75f26432833d4d3646d6fbd60ed21fa52d7e342437377973b80cf4bbeacee1980ffd0cb
+ languageName: node
+ linkType: hard
+
+"@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.24"
+ entities: "npm:^4.5.0"
+ estree-walker: "npm:^2.0.2"
+ source-map-js: "npm:^1.2.1"
+ checksum: 10c0/d5b1421c0c0cfdff6b6ae2ef3d59b5901f0fec8ad2fa153f5ae1ec8487b898c92766353c661f68b892580ab0eacbc493632c946af8141045d6e76d67797b8a84
+ languageName: node
+ linkType: hard
+
+"@vue/compiler-dom@npm:^3.5.0":
+ version: 3.5.24
+ resolution: "@vue/compiler-dom@npm:3.5.24"
+ dependencies:
+ "@vue/compiler-core": "npm:3.5.24"
+ "@vue/shared": "npm:3.5.24"
+ checksum: 10c0/d49cb715f2e1cb2272ede2e41901282fb3f6fbdf489c8aa737e60c68e21216e07b72942695a80430fee8f11e5933e36fc90615b146b189cac925bf32f2727c95
+ languageName: node
+ linkType: hard
+
+"@vue/compiler-vue2@npm:^2.7.16":
+ version: 2.7.16
+ resolution: "@vue/compiler-vue2@npm:2.7.16"
+ dependencies:
+ de-indent: "npm:^1.0.2"
+ he: "npm:^1.2.0"
+ checksum: 10c0/c76c3fad770b9a7da40b314116cc9da173da20e5fd68785c8ed8dd8a87d02f239545fa296e16552e040ec86b47bfb18283b39447b250c2e76e479bd6ae475bb3
+ languageName: node
+ linkType: hard
+
+"@vue/language-core@npm:2.2.0":
+ version: 2.2.0
+ resolution: "@vue/language-core@npm:2.2.0"
+ dependencies:
+ "@volar/language-core": "npm:~2.4.11"
+ "@vue/compiler-dom": "npm:^3.5.0"
+ "@vue/compiler-vue2": "npm:^2.7.16"
+ "@vue/shared": "npm:^3.5.0"
+ alien-signals: "npm:^0.4.9"
+ minimatch: "npm:^9.0.3"
+ muggle-string: "npm:^0.4.1"
+ path-browserify: "npm:^1.0.1"
+ peerDependencies:
+ typescript: "*"
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10c0/1c44cc4067266bbc825af358a867aed455963a08c160cd9df9a47571fd917a87d9de9bdea6149877e0c8309a6cf39f263e7cf2fbadeceba47a5a158f392151b2
+ languageName: node
+ linkType: hard
+
+"@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
@@ -6026,6 +6690,32 @@ __metadata:
languageName: node
linkType: hard
+"ajv-draft-04@npm:~1.0.0":
+ version: 1.0.0
+ resolution: "ajv-draft-04@npm:1.0.0"
+ peerDependencies:
+ ajv: ^8.5.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+ checksum: 10c0/6044310bd38c17d77549fd326bd40ce1506fa10b0794540aa130180808bf94117fac8c9b448c621512bea60e4a947278f6a978e87f10d342950c15b33ddd9271
+ languageName: node
+ linkType: hard
+
+"ajv-formats@npm:~3.0.1":
+ version: 3.0.1
+ resolution: "ajv-formats@npm:3.0.1"
+ dependencies:
+ ajv: "npm:^8.0.0"
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+ checksum: 10c0/168d6bca1ea9f163b41c8147bae537e67bd963357a5488a1eaf3abe8baa8eec806d4e45f15b10767e6020679315c7e1e5e6803088dfb84efa2b4e9353b83dd0a
+ languageName: node
+ linkType: hard
+
"ajv-keywords@npm:^3.5.2":
version: 3.5.2
resolution: "ajv-keywords@npm:3.5.2"
@@ -6047,6 +6737,49 @@ __metadata:
languageName: node
linkType: hard
+"ajv@npm:^8.0.0":
+ version: 8.17.1
+ resolution: "ajv@npm:8.17.1"
+ dependencies:
+ fast-deep-equal: "npm:^3.1.3"
+ fast-uri: "npm:^3.0.1"
+ json-schema-traverse: "npm:^1.0.0"
+ require-from-string: "npm:^2.0.2"
+ checksum: 10c0/ec3ba10a573c6b60f94639ffc53526275917a2df6810e4ab5a6b959d87459f9ef3f00d5e7865b82677cb7d21590355b34da14d1d0b9c32d75f95a187e76fff35
+ languageName: node
+ linkType: hard
+
+"ajv@npm:~8.12.0":
+ version: 8.12.0
+ resolution: "ajv@npm:8.12.0"
+ dependencies:
+ fast-deep-equal: "npm:^3.1.1"
+ json-schema-traverse: "npm:^1.0.0"
+ require-from-string: "npm:^2.0.2"
+ uri-js: "npm:^4.2.2"
+ checksum: 10c0/ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e
+ languageName: node
+ linkType: hard
+
+"ajv@npm:~8.13.0":
+ version: 8.13.0
+ resolution: "ajv@npm:8.13.0"
+ dependencies:
+ fast-deep-equal: "npm:^3.1.3"
+ json-schema-traverse: "npm:^1.0.0"
+ require-from-string: "npm:^2.0.2"
+ uri-js: "npm:^4.4.1"
+ checksum: 10c0/14c6497b6f72843986d7344175a1aa0e2c35b1e7f7475e55bc582cddb765fca7e6bf950f465dc7846f817776d9541b706f4b5b3fbedd8dfdeb5fce6f22864264
+ languageName: node
+ linkType: hard
+
+"alien-signals@npm:^0.4.9":
+ version: 0.4.14
+ resolution: "alien-signals@npm:0.4.14"
+ checksum: 10c0/5abb3377bcaf6b3819e950084b3ebd022ad90210105afb450c89dc347e80e28da441bf34858a57ea122abe7603e552ddbad80dc597c8f02a0a5206c5fb9c20cb
+ languageName: node
+ linkType: hard
+
"all-package-names@npm:^2.0.2":
version: 2.0.897
resolution: "all-package-names@npm:2.0.897"
@@ -6215,7 +6948,7 @@ __metadata:
languageName: node
linkType: hard
-"argparse@npm:^1.0.7":
+"argparse@npm:^1.0.7, argparse@npm:~1.0.9":
version: 1.0.10
resolution: "argparse@npm:1.0.10"
dependencies:
@@ -6421,13 +7154,6 @@ __metadata:
languageName: node
linkType: hard
-"assertion-error@npm:^2.0.1":
- version: 2.0.1
- resolution: "assertion-error@npm:2.0.1"
- checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8
- languageName: node
- linkType: hard
-
"ast-types-flow@npm:^0.0.8":
version: 0.0.8
resolution: "ast-types-flow@npm:0.0.8"
@@ -6950,9 +7676,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001688":
- version: 1.0.30001712
- resolution: "caniuse-lite@npm:1.0.30001712"
- checksum: 10c0/b3df8bdcc3335969380c2e47acb36c89bfc7f8fb4ef7ee2a5380e30ba46aa69e9d411654bc29894a06c201a1d60d490ab9b92787f3b66d7a7a38d71360e68215
+ version: 1.0.30001751
+ resolution: "caniuse-lite@npm:1.0.30001751"
+ checksum: 10c0/c3f2d448f3569004ace160fd9379ea0def8e7a7bc6e65611baadb57d24e1f418258647a6210e46732419f5663e2356c22aa841f92449dd3849eb6471bb7ad592
languageName: node
linkType: hard
@@ -6968,16 +7694,10 @@ __metadata:
languageName: node
linkType: hard
-"chai@npm:^5.2.0":
- version: 5.2.1
- resolution: "chai@npm:5.2.1"
- dependencies:
- assertion-error: "npm:^2.0.1"
- check-error: "npm:^2.1.1"
- deep-eql: "npm:^5.0.1"
- loupe: "npm:^3.1.0"
- pathval: "npm:^2.0.0"
- checksum: 10c0/58209c03ae9b2fd97cfa1cb0fbe372b1906e6091311b9ba1b0468cc4923b0766a50a1050a164df3ccefb9464944c9216b632f1477c9e429068013bdbb57220f6
+"chai@npm:^6.0.1":
+ version: 6.2.0
+ resolution: "chai@npm:6.2.0"
+ checksum: 10c0/a4b7d7f5907187e09f1847afa838d6d1608adc7d822031b7900813c4ed5d9702911ac2468bf290676f22fddb3d727b1be90b57c1d0a69b902534ee29cdc6ff8a
languageName: node
linkType: hard
@@ -7048,13 +7768,6 @@ __metadata:
languageName: node
linkType: hard
-"check-error@npm:^2.1.1":
- version: 2.1.1
- resolution: "check-error@npm:2.1.1"
- checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e
- languageName: node
- linkType: hard
-
"chownr@npm:^2.0.0":
version: 2.0.0
resolution: "chownr@npm:2.0.0"
@@ -7390,6 +8103,13 @@ __metadata:
languageName: node
linkType: hard
+"compare-versions@npm:^6.1.1":
+ version: 6.1.1
+ resolution: "compare-versions@npm:6.1.1"
+ checksum: 10c0/415205c7627f9e4f358f571266422980c9fe2d99086be0c9a48008ef7c771f32b0fbe8e97a441ffedc3910872f917a0675fe0fe3c3b6d331cda6d8690be06338
+ languageName: node
+ linkType: hard
+
"concat-map@npm:0.0.1":
version: 0.0.1
resolution: "concat-map@npm:0.0.1"
@@ -7397,6 +8117,20 @@ __metadata:
languageName: node
linkType: hard
+"confbox@npm:^0.1.8":
+ version: 0.1.8
+ resolution: "confbox@npm:0.1.8"
+ checksum: 10c0/fc2c68d97cb54d885b10b63e45bd8da83a8a71459d3ecf1825143dd4c7f9f1b696b3283e07d9d12a144c1301c2ebc7842380bdf0014e55acc4ae1c9550102418
+ languageName: node
+ linkType: hard
+
+"confbox@npm:^0.2.2":
+ version: 0.2.2
+ resolution: "confbox@npm:0.2.2"
+ checksum: 10c0/7c246588d533d31e8cdf66cb4701dff6de60f9be77ab54c0d0338e7988750ac56863cc0aca1b3f2046f45ff223a765d3e5d4977a7674485afcd37b6edf3fd129
+ languageName: node
+ linkType: hard
+
"config-chain@npm:^1.1.11":
version: 1.1.13
resolution: "config-chain@npm:1.1.13"
@@ -7617,6 +8351,13 @@ __metadata:
languageName: node
linkType: hard
+"css-what@npm:^6.1.0":
+ version: 6.2.2
+ resolution: "css-what@npm:6.2.2"
+ checksum: 10c0/91e24c26fb977b4ccef30d7007d2668c1c10ac0154cc3f42f7304410e9594fb772aea4f30c832d2993b132ca8d99338050866476210316345ec2e7d47b248a56
+ languageName: node
+ linkType: hard
+
"css.escape@npm:^1.5.1":
version: 1.5.1
resolution: "css.escape@npm:1.5.1"
@@ -7650,7 +8391,7 @@ __metadata:
languageName: node
linkType: hard
-"csstype@npm:^3.0.2":
+"csstype@npm:^3.0.2, csstype@npm:^3.0.7":
version: 3.1.3
resolution: "csstype@npm:3.1.3"
checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248
@@ -7728,6 +8469,13 @@ __metadata:
languageName: node
linkType: hard
+"de-indent@npm:^1.0.2":
+ version: 1.0.2
+ resolution: "de-indent@npm:1.0.2"
+ checksum: 10c0/7058ce58abd6dfc123dd204e36be3797abd419b59482a634605420f47ae97639d0c183ec5d1b904f308a01033f473673897afc2bd59bc620ebf1658763ef4291
+ languageName: node
+ linkType: hard
+
"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4":
version: 4.4.0
resolution: "debug@npm:4.4.0"
@@ -7749,6 +8497,18 @@ __metadata:
languageName: node
linkType: hard
+"debug@npm:^4.4.0, debug@npm:^4.4.3":
+ version: 4.4.3
+ resolution: "debug@npm:4.4.3"
+ dependencies:
+ ms: "npm:^2.1.3"
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6
+ languageName: node
+ linkType: hard
+
"debug@npm:^4.4.1":
version: 4.4.1
resolution: "debug@npm:4.4.1"
@@ -7815,10 +8575,15 @@ __metadata:
languageName: node
linkType: hard
-"deep-eql@npm:^5.0.1":
- version: 5.0.2
- resolution: "deep-eql@npm:5.0.2"
- checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247
+"dedent@npm:^1.5.3":
+ version: 1.7.0
+ resolution: "dedent@npm:1.7.0"
+ peerDependencies:
+ babel-plugin-macros: ^3.1.0
+ peerDependenciesMeta:
+ babel-plugin-macros:
+ optional: true
+ checksum: 10c0/c5e8a8beb5072bd5e520cb64b27a82d7ec3c2a63ee5ce47dbc2a05d5b7700cefd77a992a752cd0a8b1d979c1db06b14fb9486e805f3ad6088eda6e07cd9bf2d5
languageName: node
linkType: hard
@@ -7862,6 +8627,20 @@ __metadata:
languageName: node
linkType: hard
+"deep-object-diff@npm:^1.1.9":
+ version: 1.1.9
+ resolution: "deep-object-diff@npm:1.1.9"
+ checksum: 10c0/12cfd1b000d16c9192fc649923c972f8aac2ddca4f71a292f8f2c1e2d5cf3c9c16c85e73ab3e7d8a89a5ec6918d6460677d0b05bd160f7bd50bb4816d496dc24
+ languageName: node
+ linkType: hard
+
+"deepmerge@npm:^4.2.2":
+ version: 4.3.1
+ resolution: "deepmerge@npm:4.3.1"
+ checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044
+ languageName: node
+ linkType: hard
+
"default-browser-id@npm:^3.0.0":
version: 3.0.0
resolution: "default-browser-id@npm:3.0.0"
@@ -8050,6 +8829,13 @@ __metadata:
languageName: node
linkType: hard
+"diff@npm:~8.0.2":
+ version: 8.0.2
+ resolution: "diff@npm:8.0.2"
+ checksum: 10c0/abfb387f033e089df3ec3be960205d17b54df8abf0924d982a7ced3a94c557a4e6cbff2e78b121f216b85f466b3d8d041673a386177c311aaea41459286cc9bc
+ languageName: node
+ linkType: hard
+
"dir-glob@npm:^3.0.0, dir-glob@npm:^3.0.1":
version: 3.0.1
resolution: "dir-glob@npm:3.0.1"
@@ -8729,6 +9515,95 @@ __metadata:
languageName: node
linkType: hard
+"esbuild@npm:esbuild@>=0.17.6 <0.26.0":
+ version: 0.25.12
+ resolution: "esbuild@npm:0.25.12"
+ dependencies:
+ "@esbuild/aix-ppc64": "npm:0.25.12"
+ "@esbuild/android-arm": "npm:0.25.12"
+ "@esbuild/android-arm64": "npm:0.25.12"
+ "@esbuild/android-x64": "npm:0.25.12"
+ "@esbuild/darwin-arm64": "npm:0.25.12"
+ "@esbuild/darwin-x64": "npm:0.25.12"
+ "@esbuild/freebsd-arm64": "npm:0.25.12"
+ "@esbuild/freebsd-x64": "npm:0.25.12"
+ "@esbuild/linux-arm": "npm:0.25.12"
+ "@esbuild/linux-arm64": "npm:0.25.12"
+ "@esbuild/linux-ia32": "npm:0.25.12"
+ "@esbuild/linux-loong64": "npm:0.25.12"
+ "@esbuild/linux-mips64el": "npm:0.25.12"
+ "@esbuild/linux-ppc64": "npm:0.25.12"
+ "@esbuild/linux-riscv64": "npm:0.25.12"
+ "@esbuild/linux-s390x": "npm:0.25.12"
+ "@esbuild/linux-x64": "npm:0.25.12"
+ "@esbuild/netbsd-arm64": "npm:0.25.12"
+ "@esbuild/netbsd-x64": "npm:0.25.12"
+ "@esbuild/openbsd-arm64": "npm:0.25.12"
+ "@esbuild/openbsd-x64": "npm:0.25.12"
+ "@esbuild/openharmony-arm64": "npm:0.25.12"
+ "@esbuild/sunos-x64": "npm:0.25.12"
+ "@esbuild/win32-arm64": "npm:0.25.12"
+ "@esbuild/win32-ia32": "npm:0.25.12"
+ "@esbuild/win32-x64": "npm:0.25.12"
+ dependenciesMeta:
+ "@esbuild/aix-ppc64":
+ optional: true
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/android-arm64":
+ optional: true
+ "@esbuild/android-x64":
+ optional: true
+ "@esbuild/darwin-arm64":
+ optional: true
+ "@esbuild/darwin-x64":
+ optional: true
+ "@esbuild/freebsd-arm64":
+ optional: true
+ "@esbuild/freebsd-x64":
+ optional: true
+ "@esbuild/linux-arm":
+ optional: true
+ "@esbuild/linux-arm64":
+ optional: true
+ "@esbuild/linux-ia32":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ "@esbuild/linux-mips64el":
+ optional: true
+ "@esbuild/linux-ppc64":
+ optional: true
+ "@esbuild/linux-riscv64":
+ optional: true
+ "@esbuild/linux-s390x":
+ optional: true
+ "@esbuild/linux-x64":
+ optional: true
+ "@esbuild/netbsd-arm64":
+ optional: true
+ "@esbuild/netbsd-x64":
+ optional: true
+ "@esbuild/openbsd-arm64":
+ optional: true
+ "@esbuild/openbsd-x64":
+ optional: true
+ "@esbuild/openharmony-arm64":
+ optional: true
+ "@esbuild/sunos-x64":
+ optional: true
+ "@esbuild/win32-arm64":
+ optional: true
+ "@esbuild/win32-ia32":
+ optional: true
+ "@esbuild/win32-x64":
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b
+ languageName: node
+ linkType: hard
+
"escalade@npm:^3.1.1, escalade@npm:^3.2.0":
version: 3.2.0
resolution: "escalade@npm:3.2.0"
@@ -9073,14 +9948,7 @@ __metadata:
languageName: node
linkType: hard
-"estree-walker@npm:^1.0.1":
- version: 1.0.1
- resolution: "estree-walker@npm:1.0.1"
- checksum: 10c0/fa9e5f8c1bbe8d01e314c0f03067b64a4f22d4c58410fc5237060d0c15b81e58c23921c41acc60abbdab490f1fdfcbd6408ede2d03ca704454272e0244d61a55
- languageName: node
- linkType: hard
-
-"estree-walker@npm:^2.0.1, estree-walker@npm:^2.0.2":
+"estree-walker@npm:^2.0.2":
version: 2.0.2
resolution: "estree-walker@npm:2.0.2"
checksum: 10c0/53a6c54e2019b8c914dc395890153ffdc2322781acf4bd7d1a32d7aedc1710807bdcd866ac133903d5629ec601fbb50abe8c2e5553c7f5a0afdd9b6af6c945af
@@ -9103,6 +9971,16 @@ __metadata:
languageName: node
linkType: hard
+"eval@npm:0.1.8":
+ version: 0.1.8
+ resolution: "eval@npm:0.1.8"
+ dependencies:
+ "@types/node": "npm:*"
+ require-like: "npm:>= 0.1.1"
+ checksum: 10c0/258e700bff09e3ce3344273d5b6691b8ec5b043538d84f738f14d8b0aded33d64c00c15b380de725b1401b15f428ab35a9e7ca19a7d25f162c4f877c71586be9
+ languageName: node
+ linkType: hard
+
"execa@npm:^1.0.0":
version: 1.0.0
resolution: "execa@npm:1.0.0"
@@ -9193,7 +10071,7 @@ __metadata:
languageName: node
linkType: hard
-"expect-type@npm:^1.2.1":
+"expect-type@npm:^1.2.2":
version: 1.2.2
resolution: "expect-type@npm:1.2.2"
checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b
@@ -9220,6 +10098,13 @@ __metadata:
languageName: node
linkType: hard
+"exsolve@npm:^1.0.7":
+ version: 1.0.8
+ resolution: "exsolve@npm:1.0.8"
+ checksum: 10c0/65e44ae05bd4a4a5d87cfdbbd6b8f24389282cf9f85fa5feb17ca87ad3f354877e6af4cd99e02fc29044174891f82d1d68c77f69234410eb8f163530e6278c67
+ languageName: node
+ linkType: hard
+
"external-editor@npm:^3.0.3, external-editor@npm:^3.1.0":
version: 3.1.0
resolution: "external-editor@npm:3.1.0"
@@ -9265,6 +10150,13 @@ __metadata:
languageName: node
linkType: hard
+"fast-uri@npm:^3.0.1":
+ version: 3.1.0
+ resolution: "fast-uri@npm:3.1.0"
+ checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7
+ languageName: node
+ linkType: hard
+
"fastest-levenshtein@npm:^1.0.12":
version: 1.0.16
resolution: "fastest-levenshtein@npm:1.0.16"
@@ -9293,7 +10185,7 @@ __metadata:
languageName: node
linkType: hard
-"fdir@npm:^6.4.4, fdir@npm:^6.4.6":
+"fdir@npm:^6.4.4":
version: 6.4.6
resolution: "fdir@npm:6.4.6"
peerDependencies:
@@ -9305,6 +10197,18 @@ __metadata:
languageName: node
linkType: hard
+"fdir@npm:^6.5.0":
+ version: 6.5.0
+ resolution: "fdir@npm:6.5.0"
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+ checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f
+ languageName: node
+ linkType: hard
+
"figures@npm:^1.7.0":
version: 1.7.0
resolution: "figures@npm:1.7.0"
@@ -9358,7 +10262,7 @@ __metadata:
languageName: node
linkType: hard
-"find-cache-dir@npm:^3.3.1, find-cache-dir@npm:^3.3.2":
+"find-cache-dir@npm:^3.3.1":
version: 3.3.2
resolution: "find-cache-dir@npm:3.3.2"
dependencies:
@@ -9520,17 +10424,6 @@ __metadata:
languageName: node
linkType: hard
-"fs-extra@npm:^10.0.0":
- version: 10.1.0
- resolution: "fs-extra@npm:10.1.0"
- dependencies:
- graceful-fs: "npm:^4.2.0"
- jsonfile: "npm:^6.0.1"
- universalify: "npm:^2.0.0"
- checksum: 10c0/5f579466e7109719d162a9249abbeffe7f426eb133ea486e020b89bc6d67a741134076bf439983f2eb79276ceaf6bd7b7c1e43c3fd67fe889863e69072fb0a5e
- languageName: node
- linkType: hard
-
"fs-extra@npm:^11.0.0":
version: 11.3.0
resolution: "fs-extra@npm:11.3.0"
@@ -9554,6 +10447,17 @@ __metadata:
languageName: node
linkType: hard
+"fs-extra@npm:~11.3.0":
+ version: 11.3.2
+ resolution: "fs-extra@npm:11.3.2"
+ dependencies:
+ graceful-fs: "npm:^4.2.0"
+ jsonfile: "npm:^6.0.1"
+ universalify: "npm:^2.0.0"
+ checksum: 10c0/f5d629e1bb646d5dedb4d8b24c5aad3deb8cc1d5438979d6f237146cd10e113b49a949ae1b54212c2fbc98e2d0995f38009a9a1d0520f0287943335e65fe919b
+ languageName: node
+ linkType: hard
+
"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0":
version: 2.1.0
resolution: "fs-minipass@npm:2.1.0"
@@ -10107,6 +11011,15 @@ __metadata:
languageName: node
linkType: hard
+"he@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "he@npm:1.2.0"
+ bin:
+ he: bin/he
+ checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17
+ languageName: node
+ linkType: hard
+
"headers-polyfill@npm:^4.0.2":
version: 4.0.3
resolution: "headers-polyfill@npm:4.0.3"
@@ -10384,7 +11297,7 @@ __metadata:
languageName: node
linkType: hard
-"import-lazy@npm:^4.0.0":
+"import-lazy@npm:^4.0.0, import-lazy@npm:~4.0.0":
version: 4.0.0
resolution: "import-lazy@npm:4.0.0"
checksum: 10c0/a3520313e2c31f25c0b06aa66d167f329832b68a4f957d7c9daf6e0fa41822b6e84948191648b9b9d8ca82f94740cdf15eecf2401a5b42cd1c33fd84f2225cca
@@ -11301,6 +12214,13 @@ __metadata:
languageName: node
linkType: hard
+"javascript-stringify@npm:^2.0.1":
+ version: 2.1.0
+ resolution: "javascript-stringify@npm:2.1.0"
+ checksum: 10c0/374e74ebff29b94de78da39daa6e530999c58a145aeb293dc21180c4584459b14d9e5721d9bc6ed4eba319c437ef0145c157c946b70ecddcff6668682a002bcc
+ languageName: node
+ linkType: hard
+
"jest-canvas-mock@npm:~2.5.2":
version: 2.5.2
resolution: "jest-canvas-mock@npm:2.5.2"
@@ -11419,6 +12339,13 @@ __metadata:
languageName: node
linkType: hard
+"jju@npm:~1.4.0":
+ version: 1.4.0
+ resolution: "jju@npm:1.4.0"
+ checksum: 10c0/f3f444557e4364cfc06b1abf8331bf3778b26c0c8552ca54429bc0092652172fdea26cbffe33e1017b303d5aa506f7ede8571857400efe459cb7439180e2acad
+ languageName: node
+ linkType: hard
+
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
@@ -11550,6 +12477,13 @@ __metadata:
languageName: node
linkType: hard
+"json-schema-traverse@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "json-schema-traverse@npm:1.0.0"
+ checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6
+ languageName: node
+ linkType: hard
+
"json-stable-stringify-without-jsonify@npm:^1.0.1":
version: 1.0.1
resolution: "json-stable-stringify-without-jsonify@npm:1.0.1"
@@ -11682,6 +12616,13 @@ __metadata:
languageName: node
linkType: hard
+"kolorist@npm:^1.8.0":
+ version: 1.8.0
+ resolution: "kolorist@npm:1.8.0"
+ checksum: 10c0/73075db44a692bf6c34a649f3b4b3aea4993b84f6b754cbf7a8577e7c7db44c0bad87752bd23b0ce533f49de2244ce2ce03b7b1b667a85ae170a94782cc50f9b
+ languageName: node
+ linkType: hard
+
"language-subtag-registry@npm:^0.3.20":
version: 0.3.23
resolution: "language-subtag-registry@npm:0.3.23"
@@ -12023,6 +12964,17 @@ __metadata:
languageName: node
linkType: hard
+"local-pkg@npm:^1.0.0":
+ version: 1.1.2
+ resolution: "local-pkg@npm:1.1.2"
+ dependencies:
+ mlly: "npm:^1.7.4"
+ pkg-types: "npm:^2.3.0"
+ quansync: "npm:^0.2.11"
+ checksum: 10c0/1bcfcc5528dea95cba3caa478126a348d3985aad9f69ecf7802c13efef90897e1c5ff7851974332c5e6d4a4698efe610fef758a068c8bc3feb5322aeb35d5993
+ languageName: node
+ linkType: hard
+
"locate-path@npm:^2.0.0":
version: 2.0.0
resolution: "locate-path@npm:2.0.0"
@@ -12130,7 +13082,7 @@ __metadata:
languageName: node
linkType: hard
-"lodash@npm:^4.17.12, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21, lodash@npm:^4.17.4":
+"lodash@npm:^4.17.12, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:~4.17.15":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c
@@ -12209,13 +13161,6 @@ __metadata:
languageName: node
linkType: hard
-"loupe@npm:^3.1.0, loupe@npm:^3.1.4":
- version: 3.2.0
- resolution: "loupe@npm:3.2.0"
- checksum: 10c0/f572fd9e38db8d36ae9eede305480686e310d69bc40394b6842838ebc6c3860a0e35ab30182f33606ab2d8a685d9ff6436649269f8218a1c3385ca329973cb2c
- languageName: node
- linkType: hard
-
"lowercase-keys@npm:^2.0.0":
version: 2.0.0
resolution: "lowercase-keys@npm:2.0.0"
@@ -12289,6 +13234,15 @@ __metadata:
languageName: node
linkType: hard
+"magic-string@npm:^0.30.19":
+ version: 0.30.21
+ resolution: "magic-string@npm:0.30.21"
+ dependencies:
+ "@jridgewell/sourcemap-codec": "npm:^1.5.5"
+ checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a
+ languageName: node
+ linkType: hard
+
"magicast@npm:^0.3.5":
version: 0.3.5
resolution: "magicast@npm:0.3.5"
@@ -12414,6 +13368,15 @@ __metadata:
languageName: node
linkType: hard
+"media-query-parser@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "media-query-parser@npm:2.0.2"
+ dependencies:
+ "@babel/runtime": "npm:^7.12.5"
+ checksum: 10c0/91a987e9f6620f5c7d0fcf22bd0a106bbaccdef96aba62c461656ee656e141dd2b60f2f1d99411799183c2ea993bd177ca92c26c08bf321fbc0c846ab391d79c
+ languageName: node
+ linkType: hard
+
"memoizerific@npm:^1.11.3":
version: 1.11.3
resolution: "memoizerific@npm:1.11.3"
@@ -12547,6 +13510,15 @@ __metadata:
languageName: node
linkType: hard
+"minimatch@npm:10.0.3":
+ version: 10.0.3
+ resolution: "minimatch@npm:10.0.3"
+ dependencies:
+ "@isaacs/brace-expansion": "npm:^5.0.0"
+ checksum: 10c0/e43e4a905c5d70ac4cec8530ceaeccb9c544b1ba8ac45238e2a78121a01c17ff0c373346472d221872563204eabe929ad02669bb575cb1f0cc30facab369f70f
+ languageName: node
+ linkType: hard
+
"minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
@@ -12565,7 +13537,7 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^9.0.0, minimatch@npm:^9.0.4":
+"minimatch@npm:^9.0.0, minimatch@npm:^9.0.3, minimatch@npm:^9.0.4":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
dependencies:
@@ -12748,6 +13720,25 @@ __metadata:
languageName: node
linkType: hard
+"mlly@npm:^1.4.2, mlly@npm:^1.7.4":
+ version: 1.8.0
+ resolution: "mlly@npm:1.8.0"
+ dependencies:
+ acorn: "npm:^8.15.0"
+ pathe: "npm:^2.0.3"
+ pkg-types: "npm:^1.3.1"
+ ufo: "npm:^1.6.1"
+ checksum: 10c0/f174b844ae066c71e9b128046677868e2e28694f0bbeeffbe760b2a9d8ff24de0748d0fde6fabe706700c1d2e11d3c0d7a53071b5ea99671592fac03364604ab
+ languageName: node
+ linkType: hard
+
+"modern-ahocorasick@npm:^1.0.0":
+ version: 1.1.0
+ resolution: "modern-ahocorasick@npm:1.1.0"
+ checksum: 10c0/63fda0dab6f39886970550f5e37c4ea41cfe0c69573a7371ebc3b2db5993ed5cf4aef3e2e454e6d730992cbd4482ed9d641509c038f2ca661ccb939d822cb3ad
+ languageName: node
+ linkType: hard
+
"modify-values@npm:^1.0.0":
version: 1.0.1
resolution: "modify-values@npm:1.0.1"
@@ -12820,6 +13811,13 @@ __metadata:
languageName: node
linkType: hard
+"muggle-string@npm:^0.4.1":
+ version: 0.4.1
+ resolution: "muggle-string@npm:0.4.1"
+ checksum: 10c0/e914b63e24cd23f97e18376ec47e4ba3aa24365e4776212b666add2e47bb158003212980d732c49abf3719568900af7861873844a6e2d3a7ca7e86952c0e99e9
+ languageName: node
+ linkType: hard
+
"mute-stream@npm:0.0.7":
version: 0.0.7
resolution: "mute-stream@npm:0.0.7"
@@ -14012,6 +15010,13 @@ __metadata:
languageName: node
linkType: hard
+"path-browserify@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "path-browserify@npm:1.0.1"
+ checksum: 10c0/8b8c3fd5c66bd340272180590ae4ff139769e9ab79522e2eb82e3d571a89b8117c04147f65ad066dccfb42fcad902e5b7d794b3d35e0fd840491a8ddbedf8c66
+ languageName: node
+ linkType: hard
+
"path-exists@npm:^3.0.0":
version: 3.0.0
resolution: "path-exists@npm:3.0.0"
@@ -14092,20 +15097,13 @@ __metadata:
languageName: node
linkType: hard
-"pathe@npm:^2.0.3":
+"pathe@npm:^2.0.1, pathe@npm:^2.0.3":
version: 2.0.3
resolution: "pathe@npm:2.0.3"
checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1
languageName: node
linkType: hard
-"pathval@npm:^2.0.0":
- version: 2.0.1
- resolution: "pathval@npm:2.0.1"
- checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581
- languageName: node
- linkType: hard
-
"picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.1.1":
version: 1.1.1
resolution: "picocolors@npm:1.1.1"
@@ -14113,7 +15111,7 @@ __metadata:
languageName: node
linkType: hard
-"picomatch@npm:^2.2.2, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1":
+"picomatch@npm:^2.2.3, picomatch@npm:^2.3.1":
version: 2.3.1
resolution: "picomatch@npm:2.3.1"
checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be
@@ -14178,6 +15176,28 @@ __metadata:
languageName: node
linkType: hard
+"pkg-types@npm:^1.3.1":
+ version: 1.3.1
+ resolution: "pkg-types@npm:1.3.1"
+ dependencies:
+ confbox: "npm:^0.1.8"
+ mlly: "npm:^1.7.4"
+ pathe: "npm:^2.0.1"
+ checksum: 10c0/19e6cb8b66dcc66c89f2344aecfa47f2431c988cfa3366bdfdcfb1dd6695f87dcce37fbd90fe9d1605e2f4440b77f391e83c23255347c35cf84e7fd774d7fcea
+ languageName: node
+ linkType: hard
+
+"pkg-types@npm:^2.3.0":
+ version: 2.3.0
+ resolution: "pkg-types@npm:2.3.0"
+ dependencies:
+ confbox: "npm:^0.2.2"
+ exsolve: "npm:^1.0.7"
+ pathe: "npm:^2.0.3"
+ checksum: 10c0/d2bbddc5b81bd4741e1529c08ef4c5f1542bbdcf63498b73b8e1d84cff71806d1b8b1577800549bb569cb7aa20056257677b979bff48c97967cba7e64f72ae12
+ languageName: node
+ linkType: hard
+
"please-upgrade-node@npm:^3.1.1, please-upgrade-node@npm:^3.2.0":
version: 3.2.0
resolution: "please-upgrade-node@npm:3.2.0"
@@ -14242,7 +15262,7 @@ __metadata:
languageName: node
linkType: hard
-"prettier@npm:^3.3.3, prettier@npm:^3.5.3":
+"prettier@npm:^3.5.3":
version: 3.5.3
resolution: "prettier@npm:3.5.3"
bin:
@@ -14251,6 +15271,15 @@ __metadata:
languageName: node
linkType: hard
+"prettier@npm:^3.6.2":
+ version: 3.6.2
+ resolution: "prettier@npm:3.6.2"
+ bin:
+ prettier: bin/prettier.cjs
+ checksum: 10c0/488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812
+ languageName: node
+ linkType: hard
+
"pretty-format@npm:^27.0.0, pretty-format@npm:^27.0.2, pretty-format@npm:^27.5.1":
version: 27.5.1
resolution: "pretty-format@npm:27.5.1"
@@ -14417,6 +15446,13 @@ __metadata:
languageName: node
linkType: hard
+"quansync@npm:^0.2.11":
+ version: 0.2.11
+ resolution: "quansync@npm:0.2.11"
+ checksum: 10c0/cb9a1f8ebce074069f2f6a78578873ffedd9de9f6aa212039b44c0870955c04a71c3b1311b5d97f8ac2f2ec476de202d0a5c01160cb12bc0a11b7ef36d22ef56
+ languageName: node
+ linkType: hard
+
"query-string@npm:^6.9.0":
version: 6.14.1
resolution: "query-string@npm:6.14.1"
@@ -15037,6 +16073,20 @@ __metadata:
languageName: node
linkType: hard
+"require-from-string@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "require-from-string@npm:2.0.2"
+ checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2
+ languageName: node
+ linkType: hard
+
+"require-like@npm:>= 0.1.1":
+ version: 0.1.2
+ resolution: "require-like@npm:0.1.2"
+ checksum: 10c0/9035ff6c4000a56ede6fc51dd5c56541fafa5a7dddc9b1c3a5f9148d95ee21c603c9bf5c6e37b19fc7de13d9294260842d8590b2ffd6c7c773e78603d1af8050
+ languageName: node
+ linkType: hard
+
"requires-port@npm:^1.0.0":
version: 1.0.0
resolution: "requires-port@npm:1.0.0"
@@ -15114,6 +16164,19 @@ __metadata:
languageName: node
linkType: hard
+"resolve@npm:~1.22.1, resolve@npm:~1.22.2":
+ version: 1.22.11
+ resolution: "resolve@npm:1.22.11"
+ dependencies:
+ is-core-module: "npm:^2.16.1"
+ path-parse: "npm:^1.0.7"
+ supports-preserve-symlinks-flag: "npm:^1.0.0"
+ bin:
+ resolve: bin/resolve
+ checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409
+ languageName: node
+ linkType: hard
+
"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin":
version: 1.22.10
resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d"
@@ -15140,6 +16203,19 @@ __metadata:
languageName: node
linkType: hard
+"resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin":
+ version: 1.22.11
+ resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d"
+ dependencies:
+ is-core-module: "npm:^2.16.1"
+ path-parse: "npm:^1.0.7"
+ supports-preserve-symlinks-flag: "npm:^1.0.0"
+ bin:
+ resolve: bin/resolve
+ checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63
+ languageName: node
+ linkType: hard
+
"responselike@npm:^2.0.0":
version: 2.0.1
resolution: "responselike@npm:2.0.1"
@@ -15221,36 +16297,6 @@ __metadata:
languageName: node
linkType: hard
-"rollup-plugin-typescript2@npm:^0.36.0":
- version: 0.36.0
- resolution: "rollup-plugin-typescript2@npm:0.36.0"
- dependencies:
- "@rollup/pluginutils": "npm:^4.1.2"
- find-cache-dir: "npm:^3.3.2"
- fs-extra: "npm:^10.0.0"
- semver: "npm:^7.5.4"
- tslib: "npm:^2.6.2"
- peerDependencies:
- rollup: ">=1.26.3"
- typescript: ">=2.4.0"
- checksum: 10c0/3c8d17cd852ded36eaad2759caf170f90e091d8f86ff7b016d1823bc8b507b8f689156bcccda348fc88471681dc79cc9eb13ddb09a4dfcf0d07ac9a249e2d79b
- languageName: node
- linkType: hard
-
-"rollup@npm:^2.70.1":
- version: 2.79.2
- resolution: "rollup@npm:2.79.2"
- dependencies:
- fsevents: "npm:~2.3.2"
- dependenciesMeta:
- fsevents:
- optional: true
- bin:
- rollup: dist/bin/rollup
- checksum: 10c0/bc3746c988d903c2211266ddc539379d53d92689b9cc5c2b4e3ae161689de9af491957a567c629b6cc81f48d0928a7591fc4c383fba68a48d2966c9fb8a2bce9
- languageName: node
- linkType: hard
-
"rollup@npm:^4.20.0":
version: 4.39.0
resolution: "rollup@npm:4.39.0"
@@ -15326,30 +16372,32 @@ __metadata:
languageName: node
linkType: hard
-"rollup@npm:^4.40.0":
- version: 4.46.1
- resolution: "rollup@npm:4.46.1"
+"rollup@npm:^4.43.0":
+ version: 4.52.5
+ resolution: "rollup@npm:4.52.5"
dependencies:
- "@rollup/rollup-android-arm-eabi": "npm:4.46.1"
- "@rollup/rollup-android-arm64": "npm:4.46.1"
- "@rollup/rollup-darwin-arm64": "npm:4.46.1"
- "@rollup/rollup-darwin-x64": "npm:4.46.1"
- "@rollup/rollup-freebsd-arm64": "npm:4.46.1"
- "@rollup/rollup-freebsd-x64": "npm:4.46.1"
- "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.1"
- "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.1"
- "@rollup/rollup-linux-arm64-gnu": "npm:4.46.1"
- "@rollup/rollup-linux-arm64-musl": "npm:4.46.1"
- "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.1"
- "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.1"
- "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.1"
- "@rollup/rollup-linux-riscv64-musl": "npm:4.46.1"
- "@rollup/rollup-linux-s390x-gnu": "npm:4.46.1"
- "@rollup/rollup-linux-x64-gnu": "npm:4.46.1"
- "@rollup/rollup-linux-x64-musl": "npm:4.46.1"
- "@rollup/rollup-win32-arm64-msvc": "npm:4.46.1"
- "@rollup/rollup-win32-ia32-msvc": "npm:4.46.1"
- "@rollup/rollup-win32-x64-msvc": "npm:4.46.1"
+ "@rollup/rollup-android-arm-eabi": "npm:4.52.5"
+ "@rollup/rollup-android-arm64": "npm:4.52.5"
+ "@rollup/rollup-darwin-arm64": "npm:4.52.5"
+ "@rollup/rollup-darwin-x64": "npm:4.52.5"
+ "@rollup/rollup-freebsd-arm64": "npm:4.52.5"
+ "@rollup/rollup-freebsd-x64": "npm:4.52.5"
+ "@rollup/rollup-linux-arm-gnueabihf": "npm:4.52.5"
+ "@rollup/rollup-linux-arm-musleabihf": "npm:4.52.5"
+ "@rollup/rollup-linux-arm64-gnu": "npm:4.52.5"
+ "@rollup/rollup-linux-arm64-musl": "npm:4.52.5"
+ "@rollup/rollup-linux-loong64-gnu": "npm:4.52.5"
+ "@rollup/rollup-linux-ppc64-gnu": "npm:4.52.5"
+ "@rollup/rollup-linux-riscv64-gnu": "npm:4.52.5"
+ "@rollup/rollup-linux-riscv64-musl": "npm:4.52.5"
+ "@rollup/rollup-linux-s390x-gnu": "npm:4.52.5"
+ "@rollup/rollup-linux-x64-gnu": "npm:4.52.5"
+ "@rollup/rollup-linux-x64-musl": "npm:4.52.5"
+ "@rollup/rollup-openharmony-arm64": "npm:4.52.5"
+ "@rollup/rollup-win32-arm64-msvc": "npm:4.52.5"
+ "@rollup/rollup-win32-ia32-msvc": "npm:4.52.5"
+ "@rollup/rollup-win32-x64-gnu": "npm:4.52.5"
+ "@rollup/rollup-win32-x64-msvc": "npm:4.52.5"
"@types/estree": "npm:1.0.8"
fsevents: "npm:~2.3.2"
dependenciesMeta:
@@ -15373,7 +16421,7 @@ __metadata:
optional: true
"@rollup/rollup-linux-arm64-musl":
optional: true
- "@rollup/rollup-linux-loongarch64-gnu":
+ "@rollup/rollup-linux-loong64-gnu":
optional: true
"@rollup/rollup-linux-ppc64-gnu":
optional: true
@@ -15387,17 +16435,21 @@ __metadata:
optional: true
"@rollup/rollup-linux-x64-musl":
optional: true
+ "@rollup/rollup-openharmony-arm64":
+ optional: true
"@rollup/rollup-win32-arm64-msvc":
optional: true
"@rollup/rollup-win32-ia32-msvc":
optional: true
+ "@rollup/rollup-win32-x64-gnu":
+ optional: true
"@rollup/rollup-win32-x64-msvc":
optional: true
fsevents:
optional: true
bin:
rollup: dist/bin/rollup
- checksum: 10c0/84297d63a97bf8fc131039d0f600787ada8baaa38b744820f7d29da601bce9f70a14aa12da80843b2b49eb8660718576f441ee9952954efa16009d7ccebf0000
+ checksum: 10c0/faf1697b305d13a149bb64a2bb7378344becc7c8580f56225c4c00adbf493d82480a44b3e3b1cc82a3ac5d1d4cab6dfc89e6635443895a2dc488969075f5b94d
languageName: node
linkType: hard
@@ -15660,7 +16712,7 @@ __metadata:
languageName: node
linkType: hard
-"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.1, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1":
+"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.1, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1":
version: 7.7.1
resolution: "semver@npm:7.7.1"
bin:
@@ -15669,6 +16721,17 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:~7.5.4":
+ version: 7.5.4
+ resolution: "semver@npm:7.5.4"
+ dependencies:
+ lru-cache: "npm:^6.0.0"
+ bin:
+ semver: bin/semver.js
+ checksum: 10c0/5160b06975a38b11c1ab55950cb5b8a23db78df88275d3d8a42ccf1f29e55112ac995b3a26a522c36e3b5f76b0445f1eef70d696b8c7862a2b4303d7b0e7609e
+ languageName: node
+ linkType: hard
+
"set-blocking@npm:^2.0.0":
version: 2.0.0
resolution: "set-blocking@npm:2.0.0"
@@ -16157,7 +17220,7 @@ __metadata:
languageName: node
linkType: hard
-"string-argv@npm:^0.3.0, string-argv@npm:^0.3.1":
+"string-argv@npm:^0.3.0, string-argv@npm:^0.3.1, string-argv@npm:~0.3.1":
version: 0.3.2
resolution: "string-argv@npm:0.3.2"
checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82
@@ -16407,7 +17470,7 @@ __metadata:
languageName: node
linkType: hard
-"strip-json-comments@npm:^3.1.1":
+"strip-json-comments@npm:^3.1.1, strip-json-comments@npm:~3.1.1":
version: 3.1.1
resolution: "strip-json-comments@npm:3.1.1"
checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd
@@ -16421,15 +17484,6 @@ __metadata:
languageName: node
linkType: hard
-"strip-literal@npm:^3.0.0":
- version: 3.0.0
- resolution: "strip-literal@npm:3.0.0"
- dependencies:
- js-tokens: "npm:^9.0.1"
- checksum: 10c0/d81657f84aba42d4bbaf2a677f7e7f34c1f3de5a6726db8bc1797f9c0b303ba54d4660383a74bde43df401cf37cce1dff2c842c55b077a4ceee11f9e31fba828
- languageName: node
- linkType: hard
-
"supports-color@npm:^2.0.0":
version: 2.0.0
resolution: "supports-color@npm:2.0.0"
@@ -16455,6 +17509,15 @@ __metadata:
languageName: node
linkType: hard
+"supports-color@npm:~8.1.1":
+ version: 8.1.1
+ resolution: "supports-color@npm:8.1.1"
+ dependencies:
+ has-flag: "npm:^4.0.0"
+ checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89
+ languageName: node
+ linkType: hard
+
"supports-hyperlinks@npm:^2.2.0, supports-hyperlinks@npm:^2.3.0":
version: 2.3.0
resolution: "supports-hyperlinks@npm:2.3.0"
@@ -16676,10 +17739,13 @@ __metadata:
languageName: node
linkType: hard
-"tinypool@npm:^1.1.1":
- version: 1.1.1
- resolution: "tinypool@npm:1.1.1"
- checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b
+"tinyglobby@npm:^0.2.15":
+ version: 0.2.15
+ resolution: "tinyglobby@npm:0.2.15"
+ dependencies:
+ fdir: "npm:^6.5.0"
+ picomatch: "npm:^4.0.3"
+ checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844
languageName: node
linkType: hard
@@ -16690,10 +17756,10 @@ __metadata:
languageName: node
linkType: hard
-"tinyspy@npm:^4.0.3":
- version: 4.0.3
- resolution: "tinyspy@npm:4.0.3"
- checksum: 10c0/0a92a18b5350945cc8a1da3a22c9ad9f4e2945df80aaa0c43e1b3a3cfb64d8501e607ebf0305e048e3c3d3e0e7f8eb10cea27dc17c21effb73e66c4a3be36373
+"tinyrainbow@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "tinyrainbow@npm:3.0.3"
+ checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c
languageName: node
linkType: hard
@@ -16755,11 +17821,13 @@ __metadata:
resolution: "traefik-proxy-dashboard@workspace:."
dependencies:
"@eslint/js": "npm:^9.32.0"
+ "@noble/ed25519": "npm:^3.0.0"
+ "@noble/hashes": "npm:^2.0.1"
"@testing-library/dom": "npm:^10.4.1"
"@testing-library/jest-dom": "npm:^6.4.2"
"@testing-library/react": "npm:^14.2.1"
"@testing-library/user-event": "npm:^14.5.2"
- "@traefiklabs/faency": "npm:11.1.4"
+ "@traefiklabs/faency": "npm:12.0.4"
"@types/lodash": "npm:^4.17.16"
"@types/node": "npm:^22.15.18"
"@types/react": "npm:^18.2.0"
@@ -16768,6 +17836,7 @@ __metadata:
"@typescript-eslint/parser": "npm:^8.38.0"
"@vitejs/plugin-react": "npm:^4.7.0"
"@vitest/coverage-v8": "npm:^3.2.4"
+ "@vitest/web-worker": "npm:^4.0.2"
chart.js: "npm:^4.4.1"
eslint: "npm:^9.32.0"
eslint-config-prettier: "npm:^10.1.8"
@@ -16800,7 +17869,7 @@ __metadata:
usehooks-ts: "npm:^2.14.0"
vite: "npm:^5.4.19"
vite-tsconfig-paths: "npm:^5.1.4"
- vitest: "npm:^3.2.4"
+ vitest: "npm:^4.0.3"
vitest-canvas-mock: "npm:^0.3.3"
languageName: unknown
linkType: soft
@@ -16886,7 +17955,7 @@ __metadata:
languageName: node
linkType: hard
-"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2":
+"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
@@ -17068,13 +18137,13 @@ __metadata:
languageName: node
linkType: hard
-"typescript@npm:5.4.5":
- version: 5.4.5
- resolution: "typescript@npm:5.4.5"
+"typescript@npm:5.8.2":
+ version: 5.8.2
+ resolution: "typescript@npm:5.8.2"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: 10c0/2954022ada340fd3d6a9e2b8e534f65d57c92d5f3989a263754a78aba549f7e6529acc1921913560a4b816c46dce7df4a4d29f9f11a3dc0d4213bb76d043251e
+ checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6
languageName: node
linkType: hard
@@ -17088,13 +18157,23 @@ __metadata:
languageName: node
linkType: hard
-"typescript@patch:typescript@npm%3A5.4.5#optional!builtin":
- version: 5.4.5
- resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin::version=5.4.5&hash=5adc0c"
+"typescript@npm:^5.8.3":
+ version: 5.9.3
+ resolution: "typescript@npm:5.9.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: 10c0/db2ad2a16ca829f50427eeb1da155e7a45e598eec7b086d8b4e8ba44e5a235f758e606d681c66992230d3fc3b8995865e5fd0b22a2c95486d0b3200f83072ec9
+ checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5
+ languageName: node
+ linkType: hard
+
+"typescript@patch:typescript@npm%3A5.8.2#optional!builtin":
+ version: 5.8.2
+ resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2
languageName: node
linkType: hard
@@ -17108,6 +18187,23 @@ __metadata:
languageName: node
linkType: hard
+"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin":
+ version: 5.9.3
+ resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430
+ languageName: node
+ linkType: hard
+
+"ufo@npm:^1.6.1":
+ version: 1.6.1
+ resolution: "ufo@npm:1.6.1"
+ checksum: 10c0/5a9f041e5945fba7c189d5410508cbcbefef80b253ed29aa2e1f8a2b86f4bd51af44ee18d4485e6d3468c92be9bf4a42e3a2b72dcaf27ce39ce947ec994f1e6b
+ languageName: node
+ linkType: hard
+
"uglify-js@npm:^3.1.4":
version: 3.19.3
resolution: "uglify-js@npm:3.19.3"
@@ -17369,7 +18465,7 @@ __metadata:
languageName: node
linkType: hard
-"uri-js@npm:^4.2.2":
+"uri-js@npm:^4.2.2, uri-js@npm:^4.4.1":
version: 4.4.1
resolution: "uri-js@npm:4.4.1"
dependencies:
@@ -17519,7 +18615,7 @@ __metadata:
languageName: node
linkType: hard
-"vite-node@npm:3.2.4":
+"vite-node@npm:^3.2.2":
version: 3.2.4
resolution: "vite-node@npm:3.2.4"
dependencies:
@@ -17534,6 +18630,29 @@ __metadata:
languageName: node
linkType: hard
+"vite-plugin-dts@npm:^4.5.4":
+ version: 4.5.4
+ resolution: "vite-plugin-dts@npm:4.5.4"
+ dependencies:
+ "@microsoft/api-extractor": "npm:^7.50.1"
+ "@rollup/pluginutils": "npm:^5.1.4"
+ "@volar/typescript": "npm:^2.4.11"
+ "@vue/language-core": "npm:2.2.0"
+ compare-versions: "npm:^6.1.1"
+ debug: "npm:^4.4.0"
+ kolorist: "npm:^1.8.0"
+ local-pkg: "npm:^1.0.0"
+ magic-string: "npm:^0.30.17"
+ peerDependencies:
+ typescript: "*"
+ vite: "*"
+ peerDependenciesMeta:
+ vite:
+ optional: true
+ checksum: 10c0/5fcb7f3739d115f36195a692c0e9f9fca4e504bbbbabe29e71ee06630dd05ea2920169371e80e548eb4779d2eca14107277497838d7df588d53e1fadf84be861
+ languageName: node
+ linkType: hard
+
"vite-tsconfig-paths@npm:^5.1.4":
version: 5.1.4
resolution: "vite-tsconfig-paths@npm:5.1.4"
@@ -17550,17 +18669,17 @@ __metadata:
languageName: node
linkType: hard
-"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0":
- version: 7.0.6
- resolution: "vite@npm:7.0.6"
+"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.2
+ resolution: "vite@npm:7.2.2"
dependencies:
esbuild: "npm:^0.25.0"
- fdir: "npm:^6.4.6"
+ fdir: "npm:^6.5.0"
fsevents: "npm:~2.3.3"
picomatch: "npm:^4.0.3"
postcss: "npm:^8.5.6"
- rollup: "npm:^4.40.0"
- tinyglobby: "npm:^0.2.14"
+ rollup: "npm:^4.43.0"
+ tinyglobby: "npm:^0.2.15"
peerDependencies:
"@types/node": ^20.19.0 || >=22.12.0
jiti: ">=1.21.0"
@@ -17601,50 +18720,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
- checksum: 10c0/3b14dfa661281b4843789884199ba2a9cca940a7666970036fe3fb1abff52b88e63e8be5ab419dd04d9f96c0415ee0f1e3ec8ebe357041648af7ccd8e348b6ad
- languageName: node
- linkType: hard
-
-"vite@npm:^5.1.5":
- version: 5.4.17
- resolution: "vite@npm:5.4.17"
- dependencies:
- esbuild: "npm:^0.21.3"
- fsevents: "npm:~2.3.3"
- postcss: "npm:^8.4.43"
- rollup: "npm:^4.20.0"
- peerDependencies:
- "@types/node": ^18.0.0 || >=20.0.0
- less: "*"
- lightningcss: ^1.21.0
- sass: "*"
- sass-embedded: "*"
- stylus: "*"
- sugarss: "*"
- terser: ^5.4.0
- dependenciesMeta:
- fsevents:
- optional: true
- peerDependenciesMeta:
- "@types/node":
- optional: true
- less:
- optional: true
- lightningcss:
- optional: true
- sass:
- optional: true
- sass-embedded:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- terser:
- optional: true
- bin:
- vite: bin/vite.js
- checksum: 10c0/3322bd6d8da30cbc87b1b24cd14fdbca75abb36de81217d1062c8b4c574a1a0d28d11dfe23a3eed08b3d179d2bdc1510e0d7b9f3e1b722a45bd7631c7cec72eb
+ checksum: 10c0/9c76ee441f8dbec645ddaecc28d1f9cf35670ffa91cff69af7b1d5081545331603f0b1289d437b2fa8dc43cdc77b4d96b5bd9c9aed66310f490cb1a06f9c814c
languageName: node
linkType: hard
@@ -17691,6 +18767,61 @@ __metadata:
languageName: node
linkType: hard
+"vite@npm:^6.0.0 || ^7.0.0":
+ version: 7.1.12
+ resolution: "vite@npm:7.1.12"
+ dependencies:
+ esbuild: "npm:^0.25.0"
+ fdir: "npm:^6.5.0"
+ fsevents: "npm:~2.3.3"
+ picomatch: "npm:^4.0.3"
+ postcss: "npm:^8.5.6"
+ rollup: "npm:^4.43.0"
+ tinyglobby: "npm:^0.2.15"
+ peerDependencies:
+ "@types/node": ^20.19.0 || >=22.12.0
+ jiti: ">=1.21.0"
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: ">=0.54.8"
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+ bin:
+ vite: bin/vite.js
+ checksum: 10c0/cef4d4b4a84e663e09b858964af36e916892ac8540068df42a05ced637ceeae5e9ef71c72d54f3cfc1f3c254af16634230e221b6e2327c2a66d794bb49203262
+ languageName: node
+ linkType: hard
+
"vitest-canvas-mock@npm:^0.3.3":
version: 0.3.3
resolution: "vitest-canvas-mock@npm:0.3.3"
@@ -17702,39 +18833,38 @@ __metadata:
languageName: node
linkType: hard
-"vitest@npm:^3.2.4":
- version: 3.2.4
- resolution: "vitest@npm:3.2.4"
+"vitest@npm:^4.0.3":
+ version: 4.0.3
+ resolution: "vitest@npm:4.0.3"
dependencies:
- "@types/chai": "npm:^5.2.2"
- "@vitest/expect": "npm:3.2.4"
- "@vitest/mocker": "npm:3.2.4"
- "@vitest/pretty-format": "npm:^3.2.4"
- "@vitest/runner": "npm:3.2.4"
- "@vitest/snapshot": "npm:3.2.4"
- "@vitest/spy": "npm:3.2.4"
- "@vitest/utils": "npm:3.2.4"
- chai: "npm:^5.2.0"
- debug: "npm:^4.4.1"
- expect-type: "npm:^1.2.1"
- magic-string: "npm:^0.30.17"
+ "@vitest/expect": "npm:4.0.3"
+ "@vitest/mocker": "npm:4.0.3"
+ "@vitest/pretty-format": "npm:4.0.3"
+ "@vitest/runner": "npm:4.0.3"
+ "@vitest/snapshot": "npm:4.0.3"
+ "@vitest/spy": "npm:4.0.3"
+ "@vitest/utils": "npm:4.0.3"
+ debug: "npm:^4.4.3"
+ es-module-lexer: "npm:^1.7.0"
+ expect-type: "npm:^1.2.2"
+ magic-string: "npm:^0.30.19"
pathe: "npm:^2.0.3"
- picomatch: "npm:^4.0.2"
+ picomatch: "npm:^4.0.3"
std-env: "npm:^3.9.0"
tinybench: "npm:^2.9.0"
tinyexec: "npm:^0.3.2"
- tinyglobby: "npm:^0.2.14"
- tinypool: "npm:^1.1.1"
- tinyrainbow: "npm:^2.0.0"
- vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0"
- vite-node: "npm:3.2.4"
+ tinyglobby: "npm:^0.2.15"
+ tinyrainbow: "npm:^3.0.3"
+ vite: "npm:^6.0.0 || ^7.0.0"
why-is-node-running: "npm:^2.3.0"
peerDependencies:
"@edge-runtime/vm": "*"
"@types/debug": ^4.1.12
- "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0
- "@vitest/browser": 3.2.4
- "@vitest/ui": 3.2.4
+ "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0
+ "@vitest/browser-playwright": 4.0.3
+ "@vitest/browser-preview": 4.0.3
+ "@vitest/browser-webdriverio": 4.0.3
+ "@vitest/ui": 4.0.3
happy-dom: "*"
jsdom: "*"
peerDependenciesMeta:
@@ -17744,7 +18874,11 @@ __metadata:
optional: true
"@types/node":
optional: true
- "@vitest/browser":
+ "@vitest/browser-playwright":
+ optional: true
+ "@vitest/browser-preview":
+ optional: true
+ "@vitest/browser-webdriverio":
optional: true
"@vitest/ui":
optional: true
@@ -17754,7 +18888,14 @@ __metadata:
optional: true
bin:
vitest: vitest.mjs
- checksum: 10c0/5bf53ede3ae6a0e08956d72dab279ae90503f6b5a05298a6a5e6ef47d2fd1ab386aaf48fafa61ed07a0ebfe9e371772f1ccbe5c258dd765206a8218bf2eb79eb
+ checksum: 10c0/3462e797fc3100d6adae9d5beaa4784a109d4fe5fc1403fb251ebdf7ca95988c1f1447bbaeac3f0046aae555a611f1dcb9da0bcb9d30af7ee99ac5392427fd2b
+ languageName: node
+ linkType: hard
+
+"vscode-uri@npm:^3.0.8":
+ version: 3.1.0
+ resolution: "vscode-uri@npm:3.1.0"
+ checksum: 10c0/5f6c9c10fd9b1664d71fab4e9fbbae6be93c7f75bb3a1d9d74399a88ab8649e99691223fd7cef4644376cac6e94fa2c086d802521b9a8e31c5af3e60f0f35624
languageName: node
linkType: hard