diff --git a/App/FeatureSet/AdminDashboard/src/Pages/More/Email.tsx b/App/FeatureSet/AdminDashboard/src/Pages/More/Email.tsx index 876cc14019..b3125321a0 100644 --- a/App/FeatureSet/AdminDashboard/src/Pages/More/Email.tsx +++ b/App/FeatureSet/AdminDashboard/src/Pages/More/Email.tsx @@ -94,9 +94,8 @@ const MoreEmail: FunctionComponent = (): ReactElement => { throw new Error("Failed to send emails."); } - const data: JSONObject = response.data as JSONObject; setSuccess( - `Emails sent successfully. Total users: ${data["totalUsers"]}, Sent: ${data["sentCount"]}, Errors: ${data["errorCount"]}`, + "Broadcast email job has been started. Emails will be sent in the background.", ); } catch (err) { setError(API.getFriendlyMessage(err)); diff --git a/App/FeatureSet/AdminDashboard/src/Pages/SendEmail/Index.tsx b/App/FeatureSet/AdminDashboard/src/Pages/SendEmail/Index.tsx index a02409ad52..b5491b2900 100644 --- a/App/FeatureSet/AdminDashboard/src/Pages/SendEmail/Index.tsx +++ b/App/FeatureSet/AdminDashboard/src/Pages/SendEmail/Index.tsx @@ -51,9 +51,8 @@ const SendEmail: FunctionComponent = (): ReactElement => { throw new Error("Failed to send emails."); } - const data: JSONObject = response.data as JSONObject; setSendAllSuccess( - `Emails sent successfully. Total users: ${data["totalUsers"]}, Sent: ${data["sentCount"]}, Errors: ${data["errorCount"]}`, + "Broadcast email job has been started. Emails will be sent in the background.", ); } catch (err) { setSendAllError(API.getFriendlyMessage(err)); diff --git a/App/FeatureSet/BaseAPI/Index.ts b/App/FeatureSet/BaseAPI/Index.ts index e099f7d582..ef7b42ed48 100644 --- a/App/FeatureSet/BaseAPI/Index.ts +++ b/App/FeatureSet/BaseAPI/Index.ts @@ -29,6 +29,8 @@ import ShortLinkAPI from "Common/Server/API/ShortLinkAPI"; import StatusPageAPI from "Common/Server/API/StatusPageAPI"; import WorkspaceNotificationRuleAPI from "Common/Server/API/WorkspaceNotificationRuleAPI"; import WorkspaceNotificationSummaryAPI from "Common/Server/API/WorkspaceNotificationSummaryAPI"; +import DashboardAPI from "Common/Server/API/DashboardAPI"; +import DashboardDomainAPI from "Common/Server/API/DashboardDomainAPI"; import StatusPageDomainAPI from "Common/Server/API/StatusPageDomainAPI"; import StatusPageSubscriberAPI from "Common/Server/API/StatusPageSubscriberAPI"; import UserCallAPI from "Common/Server/API/UserCallAPI"; @@ -2073,6 +2075,16 @@ const BaseAPIFeatureSet: FeatureSet = { new StatusPageDomainAPI().getRouter(), ); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new DashboardAPI().getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new DashboardDomainAPI().getRouter(), + ); + app.use( `/${APP_NAME.toLocaleLowerCase()}`, new ProjectSsoAPI().getRouter(), diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/BlankCanvas.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/BlankCanvas.tsx index 8a7d31a3eb..110a833022 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/BlankCanvas.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/BlankCanvas.tsx @@ -21,15 +21,37 @@ const BlankCanvasElement: FunctionComponent = ( if (!props.isEditMode && props.dashboardViewConfig.components.length === 0) { return ( -
- No components added to this dashboard. Please add one to get started. +
+
+ + + +
+

No widgets yet

+

+ Click Edit to start adding charts, values, gauges, and more to this dashboard. +

); } // have a grid with width cols and height rows return ( -
+
{Array.from(Array(height).keys()).map((_: number, index: number) => { return ( = ( const widthOfUnitInPx: number = heightOfUnitInPx; // its a square - let className: string = ""; + let className: string = "transition-all duration-150"; if (props.isEditMode) { className += - "border-2 border-gray-100 rounded hover:border-gray-300 hover:bg-gray-100 cursor-pointer"; + " border border-dashed border-gray-200 rounded-md hover:border-gray-300 hover:bg-blue-50/30 cursor-pointer"; } return ( diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/ComponentSettingsSideOver.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/ComponentSettingsSideOver.tsx index 31505a7ceb..24d6d3f6c5 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/ComponentSettingsSideOver.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/ComponentSettingsSideOver.tsx @@ -55,7 +55,7 @@ const ComponentSettingsSideOver: FunctionComponent = ( }} leftFooterElement={
@@ -353,7 +366,7 @@ const DashboardBaseComponentElement: FunctionComponent = ( window.addEventListener("mousemove", resizeHeight); window.addEventListener("mouseup", stopResizeAndMove); }} - className={`resize-height-element ${resizeCursorIcon} absolute bottom-0 left-0 w-12 h-2 bg-blue-300 hover:bg-blue-400 rounded-full cursor-pointer`} + className={`resize-height-element ${resizeCursorIcon} absolute bottom-0 left-0 w-10 h-1.5 bg-blue-400 hover:bg-blue-500 rounded-full cursor-pointer transition-colors duration-150 opacity-70 hover:opacity-100`} >
); }; @@ -373,6 +386,7 @@ const DashboardBaseComponentElement: FunctionComponent = ( widthOfComponent + (SpaceBetweenUnitsInPx - 2) * (widthOfComponent - 1) }px`, + boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.04), 0 1px 2px -1px rgba(0, 0, 0, 0.03)", }} key={component.componentId?.toString() || Math.random().toString()} ref={dashboardComponentRef} @@ -380,6 +394,15 @@ const DashboardBaseComponentElement: FunctionComponent = ( > {getMoveElement()} + {/* Component type badge - visible in edit mode */} + {props.isEditMode && props.isSelected && ( +
+ + {component.componentType} + +
+ )} + {component.componentType === DashboardComponentType.Text && ( = ( component={component as DashboardValueComponentType} /> )} + {component.componentType === DashboardComponentType.Table && ( + + )} + {component.componentType === DashboardComponentType.Gauge && ( + + )} + {component.componentType === DashboardComponentType.LogStream && ( + + )} + {component.componentType === DashboardComponentType.TraceList && ( + + )} {getResizeWidthElement()} {getResizeHeightElement()} diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx index 8230e13d95..0952c5485a 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx @@ -3,12 +3,10 @@ import DashboardChartComponent from "Common/Types/Dashboard/DashboardComponents/ import { DashboardBaseComponentProps } from "./DashboardBaseComponent"; import MetricCharts from "../../Metrics/MetricCharts"; import AggregatedResult from "Common/Types/BaseDatabase/AggregatedResult"; -import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage"; import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; import MetricViewData from "Common/Types/Metrics/MetricViewData"; import MetricUtil from "../../Metrics/Utils/Metrics"; import API from "Common/UI/Utils/API/API"; -import ComponentLoader from "Common/UI/Components/ComponentLoader/ComponentLoader"; import JSONFunctions from "Common/Types/JSONFunctions"; import MetricQueryConfigData, { MetricChartType, @@ -31,10 +29,24 @@ const DashboardChartComponentElement: FunctionComponent = ( const [error, setError] = React.useState(null); const [isLoading, setIsLoading] = React.useState(true); + // Resolve query configs - support both single and multi-query + const resolveQueryConfigs: () => Array = () => { + if ( + props.component.arguments.metricQueryConfigs && + props.component.arguments.metricQueryConfigs.length > 0 + ) { + return props.component.arguments.metricQueryConfigs; + } + if (props.component.arguments.metricQueryConfig) { + return [props.component.arguments.metricQueryConfig]; + } + return []; + }; + + const queryConfigs: Array = resolveQueryConfigs(); + const metricViewData: MetricViewData = { - queryConfigs: props.component.arguments.metricQueryConfig - ? [props.component.arguments.metricQueryConfig] - : [], + queryConfigs: queryConfigs, startAndEndDate: RangeStartAndEndDateTimeUtil.getStartAndEndDate( props.dashboardStartAndEndDate, ), @@ -97,40 +109,71 @@ const DashboardChartComponentElement: FunctionComponent = ( useEffect(() => { fetchAggregatedResults(); - }, [props.dashboardStartAndEndDate, props.metricTypes]); + }, [props.dashboardStartAndEndDate, props.metricTypes, props.refreshTick]); - const [metricQueryConfig, setMetricQueryConfig] = React.useState< - MetricQueryConfigData | undefined - >(props.component.arguments.metricQueryConfig); + const [prevQueryConfigs, setPrevQueryConfigs] = React.useState< + Array | MetricQueryConfigData | undefined + >( + props.component.arguments.metricQueryConfigs || + props.component.arguments.metricQueryConfig, + ); useEffect(() => { - // set metricQueryConfig to the new value only if it is different from the previous value + const currentConfigs: + | Array + | MetricQueryConfigData + | undefined = + props.component.arguments.metricQueryConfigs || + props.component.arguments.metricQueryConfig; + if ( JSONFunctions.isJSONObjectDifferent( - metricQueryConfig || {}, - props.component.arguments.metricQueryConfig || {}, + prevQueryConfigs || {}, + currentConfigs || {}, ) ) { - setMetricQueryConfig(props.component.arguments.metricQueryConfig); + setPrevQueryConfigs(currentConfigs); fetchAggregatedResults(); } - }, [props.component.arguments.metricQueryConfig]); - - useEffect(() => { - fetchAggregatedResults(); - }, []); + }, [ + props.component.arguments.metricQueryConfig, + props.component.arguments.metricQueryConfigs, + ]); if (isLoading) { - return ; + // Skeleton loading for chart + return ( +
+
+
+ {Array.from({ length: 12 }).map((_: unknown, i: number) => { + return ( +
+ ); + })} +
+
+ ); } if (error) { return ( -
-
- +
+
+
+ +
- +

+ {error} +

); } @@ -142,35 +185,57 @@ const DashboardChartComponentElement: FunctionComponent = ( heightOfChart = undefined; } - // add title and description. - type GetMetricChartType = () => MetricChartType; - // Convert dashboard chart type to metric chart type const getMetricChartType: GetMetricChartType = (): MetricChartType => { if (props.component.arguments.chartType === DashboardChartType.Bar) { return MetricChartType.BAR; } + if ( + props.component.arguments.chartType === DashboardChartType.Area || + props.component.arguments.chartType === DashboardChartType.StackedArea + ) { + return MetricChartType.AREA; + } return MetricChartType.LINE; }; const chartMetricViewData: MetricViewData = { - queryConfigs: props.component.arguments.metricQueryConfig - ? [ - { - ...props.component.arguments.metricQueryConfig!, + queryConfigs: queryConfigs.map( + (config: MetricQueryConfigData, index: number) => { + // For the first query, apply the chart-level title/description/legend + if (index === 0) { + return { + ...config, metricAliasData: { - title: props.component.arguments.chartTitle || undefined, + title: + config.metricAliasData?.title || + props.component.arguments.chartTitle || + undefined, description: - props.component.arguments.chartDescription || undefined, - metricVariable: undefined, - legend: props.component.arguments.legendText || undefined, - legendUnit: props.component.arguments.legendUnit || undefined, + config.metricAliasData?.description || + props.component.arguments.chartDescription || + undefined, + metricVariable: + config.metricAliasData?.metricVariable || undefined, + legend: + config.metricAliasData?.legend || + props.component.arguments.legendText || + undefined, + legendUnit: + config.metricAliasData?.legendUnit || + props.component.arguments.legendUnit || + undefined, }, - chartType: getMetricChartType(), - }, - ] - : [], + chartType: config.chartType || getMetricChartType(), + }; + } + return { + ...config, + chartType: config.chartType || getMetricChartType(), + }; + }, + ), startAndEndDate: RangeStartAndEndDateTimeUtil.getStartAndEndDate( props.dashboardStartAndEndDate, ), @@ -178,7 +243,7 @@ const DashboardChartComponentElement: FunctionComponent = ( }; return ( -
+
= ( + props: ComponentProps, +): ReactElement => { + const [metricResults, setMetricResults] = React.useState< + Array + >([]); + const [aggregationType, setAggregationType] = + React.useState(AggregationType.Avg); + const [error, setError] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(true); + + const metricViewData: MetricViewData = { + queryConfigs: props.component.arguments.metricQueryConfig + ? [props.component.arguments.metricQueryConfig] + : [], + startAndEndDate: RangeStartAndEndDateTimeUtil.getStartAndEndDate( + props.dashboardStartAndEndDate, + ), + formulaConfigs: [], + }; + + const fetchAggregatedResults: PromiseVoidFunction = + async (): Promise => { + setIsLoading(true); + + if ( + !metricViewData.startAndEndDate?.startValue || + !metricViewData.startAndEndDate?.endValue + ) { + setIsLoading(false); + return; + } + + if ( + !metricViewData.queryConfigs || + metricViewData.queryConfigs.length === 0 || + !metricViewData.queryConfigs[0] || + !metricViewData.queryConfigs[0].metricQueryData || + !metricViewData.queryConfigs[0].metricQueryData.filterData || + Object.keys(metricViewData.queryConfigs[0].metricQueryData.filterData) + .length === 0 + ) { + setIsLoading(false); + return; + } + + if ( + !metricViewData.queryConfigs[0] || + !metricViewData.queryConfigs[0].metricQueryData.filterData || + !metricViewData.queryConfigs[0].metricQueryData.filterData + ?.aggegationType + ) { + setIsLoading(false); + return; + } + + setAggregationType( + (metricViewData.queryConfigs[0].metricQueryData.filterData + ?.aggegationType as AggregationType) || AggregationType.Avg, + ); + + try { + const results: Array = await MetricUtil.fetchResults({ + metricViewData: metricViewData, + }); + + setMetricResults(results); + setError(""); + } catch (err: unknown) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + }; + + const [metricQueryConfig, setMetricQueryConfig] = React.useState< + MetricQueryConfigData | undefined + >(props.component.arguments.metricQueryConfig); + + useEffect(() => { + fetchAggregatedResults(); + }, [props.dashboardStartAndEndDate, props.metricTypes, props.refreshTick]); + + useEffect(() => { + if ( + JSONFunctions.isJSONObjectDifferent( + metricQueryConfig || {}, + props.component.arguments.metricQueryConfig || {}, + ) + ) { + setMetricQueryConfig(props.component.arguments.metricQueryConfig); + fetchAggregatedResults(); + } + }, [props.component.arguments.metricQueryConfig]); + + if (isLoading) { + // Skeleton loading for gauge + return ( +
+
+
+
+
+ ); + } + + if (error) { + return ( +
+
+ + + +
+

{error}

+
+ ); + } + + // Show setup state if no metric configured + if ( + !props.component.arguments.metricQueryConfig || + !props.component.arguments.metricQueryConfig.metricQueryData?.filterData || + Object.keys( + props.component.arguments.metricQueryConfig.metricQueryData.filterData, + ).length === 0 + ) { + return ( +
+
+ + + +
+

+ {props.component.arguments.gaugeTitle || "Gauge Widget"} +

+

+ Click to configure metric +

+
+ ); + } + + // Calculate aggregated value + let aggregatedValue: number = 0; + let avgCount: number = 0; + + for (const result of metricResults) { + for (const item of result.data) { + const value: number = item.value; + + if (aggregationType === AggregationType.Avg) { + aggregatedValue += value; + avgCount += 1; + } else if (aggregationType === AggregationType.Sum) { + aggregatedValue += value; + } else if (aggregationType === AggregationType.Min) { + aggregatedValue = Math.min(aggregatedValue, value); + } else if (aggregationType === AggregationType.Max) { + aggregatedValue = Math.max(aggregatedValue, value); + } else if (aggregationType === AggregationType.Count) { + aggregatedValue += 1; + } + } + } + + if (aggregationType === AggregationType.Avg && avgCount > 0) { + aggregatedValue = aggregatedValue / avgCount; + } + + aggregatedValue = Math.round(aggregatedValue * 100) / 100; + + const minValue: number = props.component.arguments.minValue ?? 0; + const maxValue: number = props.component.arguments.maxValue ?? 100; + const warningThreshold: number | undefined = + props.component.arguments.warningThreshold; + const criticalThreshold: number | undefined = + props.component.arguments.criticalThreshold; + + // Calculate percentage for the gauge arc + const range: number = maxValue - minValue; + const percentage: number = + range > 0 + ? Math.min(Math.max((aggregatedValue - minValue) / range, 0), 1) + : 0; + + // Determine color based on thresholds + let gaugeColor: string = "#10b981"; // green + if ( + criticalThreshold !== undefined && + aggregatedValue >= criticalThreshold + ) { + gaugeColor = "#ef4444"; // red + } else if ( + warningThreshold !== undefined && + aggregatedValue >= warningThreshold + ) { + gaugeColor = "#f59e0b"; // yellow + } + + // SVG gauge rendering + const size: number = Math.min( + props.dashboardComponentWidthInPx - 40, + props.dashboardComponentHeightInPx - 60, + ); + const gaugeSize: number = Math.max(size, 80); + const strokeWidth: number = Math.max(gaugeSize * 0.1, 8); + const radius: number = (gaugeSize - strokeWidth) / 2; + const centerX: number = gaugeSize / 2; + const centerY: number = gaugeSize / 2; + + // Semi-circle arc (180 degrees, from left to right) + const startAngle: number = Math.PI; + const endAngle: number = 0; + const sweepAngle: number = startAngle - endAngle; + const currentAngle: number = startAngle - sweepAngle * percentage; + + const arcStartX: number = centerX + radius * Math.cos(startAngle); + const arcStartY: number = centerY - radius * Math.sin(startAngle); + const arcEndX: number = centerX + radius * Math.cos(endAngle); + const arcEndY: number = centerY - radius * Math.sin(endAngle); + const arcCurrentX: number = centerX + radius * Math.cos(currentAngle); + const arcCurrentY: number = centerY - radius * Math.sin(currentAngle); + + const backgroundPath: string = `M ${arcStartX} ${arcStartY} A ${radius} ${radius} 0 0 1 ${arcEndX} ${arcEndY}`; + const valuePath: string = `M ${arcStartX} ${arcStartY} A ${radius} ${radius} 0 ${percentage > 0.5 ? 1 : 0} 1 ${arcCurrentX} ${arcCurrentY}`; + + const titleHeightInPx: number = Math.min( + Math.max(props.dashboardComponentHeightInPx * 0.1, 12), + 16, + ); + const valueHeightInPx: number = Math.max(gaugeSize * 0.22, 16); + + // Generate a unique gradient ID for this component instance + const gradientId: string = `gauge-gradient-${props.componentId?.toString() || "default"}`; + + // Threshold marker positions on arc + type ThresholdMarker = { + angle: number; + x: number; + y: number; + color: string; + }; + + const thresholdMarkers: Array = []; + + if (warningThreshold !== undefined && range > 0) { + const warningPct: number = Math.min( + Math.max((warningThreshold - minValue) / range, 0), + 1, + ); + const warningAngle: number = startAngle - sweepAngle * warningPct; + thresholdMarkers.push({ + angle: warningAngle, + x: centerX + (radius + strokeWidth * 0.7) * Math.cos(warningAngle), + y: centerY - (radius + strokeWidth * 0.7) * Math.sin(warningAngle), + color: "#f59e0b", + }); + } + + if (criticalThreshold !== undefined && range > 0) { + const criticalPct: number = Math.min( + Math.max((criticalThreshold - minValue) / range, 0), + 1, + ); + const criticalAngle: number = startAngle - sweepAngle * criticalPct; + thresholdMarkers.push({ + angle: criticalAngle, + x: centerX + (radius + strokeWidth * 0.7) * Math.cos(criticalAngle), + y: centerY - (radius + strokeWidth * 0.7) * Math.sin(criticalAngle), + color: "#ef4444", + }); + } + + const percentDisplay: number = Math.round(percentage * 100); + + return ( +
+ {props.component.arguments.gaugeTitle && ( +
0 ? `${titleHeightInPx}px` : "", + }} + className="text-center font-medium text-gray-400 mb-2 truncate uppercase tracking-wider" + > + {props.component.arguments.gaugeTitle} +
+ )} + + + + + + + + + + + + + + + + {/* Background track */} + + {/* Value arc */} + {percentage > 0 && ( + + )} + {/* Threshold markers */} + {thresholdMarkers.map( + (marker: ThresholdMarker, index: number) => { + return ( + + ); + }, + )} + {/* Needle tip dot at current position */} + {percentage > 0 && ( + + )} + + {/* Value + percentage display */} +
+
0 ? `${valueHeightInPx}px` : "", + lineHeight: 1.1, + letterSpacing: "-0.03em", + }} + > + {aggregatedValue} +
+
+ {percentDisplay}% +
+
+ {/* Min/Max labels */} +
+ + {minValue} + + + {maxValue} + +
+
+ ); +}; + +export default DashboardGaugeComponentElement; diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardLogStreamComponent.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardLogStreamComponent.tsx new file mode 100644 index 0000000000..6f9804179d --- /dev/null +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardLogStreamComponent.tsx @@ -0,0 +1,267 @@ +import React, { FunctionComponent, ReactElement, useEffect } from "react"; +import DashboardLogStreamComponent from "Common/Types/Dashboard/DashboardComponents/DashboardLogStreamComponent"; +import { DashboardBaseComponentProps } from "./DashboardBaseComponent"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import AnalyticsModelAPI, { + ListResult, +} from "Common/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI"; +import Log from "Common/Models/AnalyticsModels/Log"; +import API from "Common/UI/Utils/API/API"; +import Icon from "Common/UI/Components/Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import { RangeStartAndEndDateTimeUtil } from "Common/Types/Time/RangeStartAndEndDateTime"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import OneUptimeDate from "Common/Types/Date"; +import Query from "Common/Types/BaseDatabase/Query"; +import { queryStringToFilter, LogFilter } from "Common/Types/Log/LogQueryToFilter"; + +export interface ComponentProps extends DashboardBaseComponentProps { + component: DashboardLogStreamComponent; +} + +type SeverityColor = { + dot: string; + text: string; + bg: string; +}; + +const getSeverityColor: (severity: string) => SeverityColor = ( + severity: string, +): SeverityColor => { + const lower: string = severity.toLowerCase(); + if (lower === "fatal") { + return { dot: "bg-purple-500", text: "text-purple-700", bg: "bg-purple-50" }; + } + if (lower === "error") { + return { dot: "bg-red-500", text: "text-red-700", bg: "bg-red-50" }; + } + if (lower === "warning") { + return { dot: "bg-yellow-500", text: "text-yellow-700", bg: "bg-yellow-50" }; + } + if (lower === "information") { + return { dot: "bg-blue-500", text: "text-blue-700", bg: "bg-blue-50" }; + } + if (lower === "debug") { + return { dot: "bg-gray-400", text: "text-gray-600", bg: "bg-gray-50" }; + } + if (lower === "trace") { + return { dot: "bg-gray-300", text: "text-gray-500", bg: "bg-gray-50" }; + } + return { dot: "bg-gray-300", text: "text-gray-500", bg: "bg-gray-50" }; +}; + +const DashboardLogStreamComponentElement: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + const [logs, setLogs] = React.useState>([]); + const [error, setError] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(true); + + const maxRows: number = props.component.arguments.maxRows || 50; + + const fetchLogs: PromiseVoidFunction = async (): Promise => { + setIsLoading(true); + + const startAndEndDate: InBetween = + RangeStartAndEndDateTimeUtil.getStartAndEndDate( + props.dashboardStartAndEndDate, + ); + + if (!startAndEndDate.startValue || !startAndEndDate.endValue) { + setIsLoading(false); + setError("Please select a valid start and end date."); + return; + } + + try { + const query: Query = { + time: new InBetween( + startAndEndDate.startValue, + startAndEndDate.endValue, + ), + } as Query; + + // Add severity filter if set + if ( + props.component.arguments.severityFilter && + props.component.arguments.severityFilter !== "" + ) { + (query as Record)["severityText"] = + props.component.arguments.severityFilter; + } + + // Add body contains filter if set + if ( + props.component.arguments.bodyContains && + props.component.arguments.bodyContains.trim() !== "" + ) { + (query as Record)["body"] = + props.component.arguments.bodyContains.trim(); + } + + // Add attribute filters if set + if ( + props.component.arguments.attributeFilterQuery && + props.component.arguments.attributeFilterQuery.trim() !== "" + ) { + const parsedFilter: LogFilter = queryStringToFilter( + props.component.arguments.attributeFilterQuery.trim(), + ); + + if (parsedFilter.attributes) { + (query as Record)["attributes"] = + parsedFilter.attributes; + } + } + + const listResult: ListResult = + await AnalyticsModelAPI.getList({ + modelType: Log, + query: query, + limit: maxRows, + skip: 0, + select: { + time: true, + severityText: true, + body: true, + serviceId: true, + traceId: true, + spanId: true, + attributes: true, + }, + sort: { + time: SortOrder.Descending, + }, + requestOptions: {}, + }); + + setLogs(listResult.data); + setError(""); + } catch (err: unknown) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchLogs(); + }, [props.dashboardStartAndEndDate, props.refreshTick]); + + useEffect(() => { + fetchLogs(); + }, [ + props.component.arguments.severityFilter, + props.component.arguments.bodyContains, + props.component.arguments.attributeFilterQuery, + props.component.arguments.maxRows, + ]); + + if (isLoading) { + return ( +
+
+
+ {Array.from({ length: 6 }).map((_: unknown, i: number) => { + return ( +
+
+
+
+
+ ); + })} +
+
+ ); + } + + if (error) { + return ( +
+
+
+ +
+
+

{error}

+
+ ); + } + + return ( +
+ {props.component.arguments.title && ( +
+ + {props.component.arguments.title} + + + {logs.length} entries + +
+ )} +
+
+ {logs.map((log: Log, index: number) => { + const severity: string = + (log.severityText as string) || "Unspecified"; + const colors: SeverityColor = getSeverityColor(severity); + const body: string = (log.body as string) || ""; + const time: Date | undefined = log.time + ? OneUptimeDate.fromString(log.time as string) + : undefined; + + return ( +
+
+
+ + {severity.substring(0, 4).toUpperCase()} + +
+ {time && ( + + {OneUptimeDate.getDateAsLocalFormattedString(time, true)} + + )} + + {body} + +
+ ); + })} + {logs.length === 0 && ( +
+ No logs found +
+ )} +
+
+
+ ); +}; + +export default DashboardLogStreamComponentElement; diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTableComponent.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTableComponent.tsx new file mode 100644 index 0000000000..35686c6634 --- /dev/null +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTableComponent.tsx @@ -0,0 +1,252 @@ +import React, { FunctionComponent, ReactElement, useEffect } from "react"; +import DashboardTableComponent from "Common/Types/Dashboard/DashboardComponents/DashboardTableComponent"; +import { DashboardBaseComponentProps } from "./DashboardBaseComponent"; +import AggregatedResult from "Common/Types/BaseDatabase/AggregatedResult"; +import AggregatedModel from "Common/Types/BaseDatabase/AggregatedModel"; +import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import MetricViewData from "Common/Types/Metrics/MetricViewData"; +import MetricUtil from "../../Metrics/Utils/Metrics"; +import API from "Common/UI/Utils/API/API"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import MetricQueryConfigData from "Common/Types/Metrics/MetricQueryConfigData"; +import Icon from "Common/UI/Components/Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import { RangeStartAndEndDateTimeUtil } from "Common/Types/Time/RangeStartAndEndDateTime"; +import OneUptimeDate from "Common/Types/Date"; + +export interface ComponentProps extends DashboardBaseComponentProps { + component: DashboardTableComponent; +} + +const DashboardTableComponentElement: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + const [metricResults, setMetricResults] = React.useState< + Array + >([]); + const [error, setError] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(true); + + const metricViewData: MetricViewData = { + queryConfigs: props.component.arguments.metricQueryConfig + ? [props.component.arguments.metricQueryConfig] + : [], + startAndEndDate: RangeStartAndEndDateTimeUtil.getStartAndEndDate( + props.dashboardStartAndEndDate, + ), + formulaConfigs: [], + }; + + const fetchAggregatedResults: PromiseVoidFunction = + async (): Promise => { + setIsLoading(true); + + if ( + !metricViewData.startAndEndDate?.startValue || + !metricViewData.startAndEndDate?.endValue + ) { + setIsLoading(false); + setError("Please select a valid start and end date."); + return; + } + + if ( + !metricViewData.queryConfigs || + metricViewData.queryConfigs.length === 0 || + !metricViewData.queryConfigs[0] || + !metricViewData.queryConfigs[0].metricQueryData || + !metricViewData.queryConfigs[0].metricQueryData.filterData || + Object.keys(metricViewData.queryConfigs[0].metricQueryData.filterData) + .length === 0 + ) { + setIsLoading(false); + setError("Please select a metric. Click here to add a metric."); + return; + } + + if ( + !metricViewData.queryConfigs[0] || + !metricViewData.queryConfigs[0].metricQueryData.filterData || + !metricViewData.queryConfigs[0].metricQueryData.filterData + ?.aggegationType + ) { + setIsLoading(false); + setError( + "Please select an aggregation. Click here to add an aggregation.", + ); + return; + } + + try { + const results: Array = await MetricUtil.fetchResults({ + metricViewData: metricViewData, + }); + + setMetricResults(results); + setError(""); + } catch (err: unknown) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchAggregatedResults(); + }, [props.dashboardStartAndEndDate, props.metricTypes, props.refreshTick]); + + const [metricQueryConfig, setMetricQueryConfig] = React.useState< + MetricQueryConfigData | undefined + >(props.component.arguments.metricQueryConfig); + + useEffect(() => { + if ( + JSONFunctions.isJSONObjectDifferent( + metricQueryConfig || {}, + props.component.arguments.metricQueryConfig || {}, + ) + ) { + setMetricQueryConfig(props.component.arguments.metricQueryConfig); + fetchAggregatedResults(); + } + }, [props.component.arguments.metricQueryConfig]); + + if (isLoading) { + // Skeleton loading for table + return ( +
+
+
+
+
+
+
+ {Array.from({ length: 5 }).map((_: unknown, i: number) => { + return ( +
+
+
+
+ ); + })} +
+
+ ); + } + + if (error) { + return ( +
+
+
+ +
+
+ +
+ ); + } + + const maxRows: number = props.component.arguments.maxRows || 20; + + const allData: Array = []; + for (const result of metricResults) { + for (const item of result.data) { + allData.push(item); + } + } + + const displayData: Array = allData.slice(0, maxRows); + + // Calculate max value for bar visualization + const maxDataValue: number = + displayData.length > 0 + ? Math.max( + ...displayData.map((item: AggregatedModel) => { + return Math.abs(item.value); + }), + ) + : 1; + + return ( +
+ {props.component.arguments.tableTitle && ( +
+ + {props.component.arguments.tableTitle} + + + {displayData.length} rows + +
+ )} +
+ + + + + + + + + + {displayData.map((item: AggregatedModel, index: number) => { + const roundedValue: number = + Math.round(item.value * 100) / 100; + const barWidth: number = + maxDataValue > 0 + ? (Math.abs(roundedValue) / maxDataValue) * 100 + : 0; + + return ( + + + + + + ); + })} + {displayData.length === 0 && ( + + + + )} + +
+ Timestamp + + Value + +
+ {OneUptimeDate.getDateAsLocalFormattedString( + OneUptimeDate.fromString(item.timestamp), + )} + + {roundedValue} + +
+
+
+
+ No data available +
+
+
+ ); +}; + +export default DashboardTableComponentElement; diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTextComponent.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTextComponent.tsx index 05f65b34ea..5f0e321f38 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTextComponent.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTextComponent.tsx @@ -1,6 +1,7 @@ import React, { FunctionComponent, ReactElement } from "react"; import DashboardTextComponent from "Common/Types/Dashboard/DashboardComponents/DashboardTextComponent"; import { DashboardBaseComponentProps } from "./DashboardBaseComponent"; +import LazyMarkdownViewer from "Common/UI/Components/Markdown.tsx/LazyMarkdownViewer"; export interface ComponentProps extends DashboardBaseComponentProps { component: DashboardTextComponent; @@ -9,18 +10,28 @@ export interface ComponentProps extends DashboardBaseComponentProps { const DashboardTextComponentElement: FunctionComponent = ( props: ComponentProps, ): ReactElement => { - const textClassName: string = `m-auto truncate flex flex-col justify-center h-full ${props.component.arguments.isBold ? "font-medium" : ""} ${props.component.arguments.isItalic ? "italic" : ""} ${props.component.arguments.isUnderline ? "underline" : ""}`; - const textHeightInxPx: number = props.dashboardComponentHeightInPx * 0.4; + if (props.component.arguments.isMarkdown) { + return ( +
+ +
+ ); + } + + const textClassName: string = `flex items-center justify-center h-full text-gray-800 leading-snug ${props.component.arguments.isBold ? "font-semibold" : "font-normal"} ${props.component.arguments.isItalic ? "italic" : ""} ${props.component.arguments.isUnderline ? "underline decoration-gray-300 underline-offset-4" : ""}`; + const textHeightInxPx: number = Math.min(props.dashboardComponentHeightInPx * 0.35, 64); return ( -
+
0 ? `${textHeightInxPx}px` : "", }} > - {props.component.arguments.text} + {props.component.arguments.text || ( + No text configured + )}
); diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTraceListComponent.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTraceListComponent.tsx new file mode 100644 index 0000000000..6bfb70b6fc --- /dev/null +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardTraceListComponent.tsx @@ -0,0 +1,294 @@ +import React, { FunctionComponent, ReactElement, useEffect } from "react"; +import DashboardTraceListComponent from "Common/Types/Dashboard/DashboardComponents/DashboardTraceListComponent"; +import { DashboardBaseComponentProps } from "./DashboardBaseComponent"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import AnalyticsModelAPI, { + ListResult, +} from "Common/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI"; +import Span, { SpanStatus } from "Common/Models/AnalyticsModels/Span"; +import API from "Common/UI/Utils/API/API"; +import Icon from "Common/UI/Components/Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import { RangeStartAndEndDateTimeUtil } from "Common/Types/Time/RangeStartAndEndDateTime"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import OneUptimeDate from "Common/Types/Date"; +import Query from "Common/Types/BaseDatabase/Query"; + +export interface ComponentProps extends DashboardBaseComponentProps { + component: DashboardTraceListComponent; +} + +type StatusStyle = { + label: string; + textClass: string; + bgClass: string; +}; + +const getStatusStyle: (statusCode: number) => StatusStyle = ( + statusCode: number, +): StatusStyle => { + if (statusCode === SpanStatus.Error) { + return { + label: "Error", + textClass: "text-red-700", + bgClass: "bg-red-50 border-red-100", + }; + } + if (statusCode === SpanStatus.Ok) { + return { + label: "Ok", + textClass: "text-green-700", + bgClass: "bg-green-50 border-green-100", + }; + } + return { + label: "Unset", + textClass: "text-gray-500", + bgClass: "bg-gray-50 border-gray-100", + }; +}; + +const formatDuration: (durationNano: number) => string = ( + durationNano: number, +): string => { + if (durationNano < 1000) { + return `${durationNano}ns`; + } + const durationMicro: number = durationNano / 1000; + if (durationMicro < 1000) { + return `${Math.round(durationMicro)}µs`; + } + const durationMs: number = durationMicro / 1000; + if (durationMs < 1000) { + return `${Math.round(durationMs * 10) / 10}ms`; + } + const durationS: number = durationMs / 1000; + return `${Math.round(durationS * 100) / 100}s`; +}; + +const DashboardTraceListComponentElement: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + const [spans, setSpans] = React.useState>([]); + const [error, setError] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(true); + + const maxRows: number = props.component.arguments.maxRows || 50; + + const fetchTraces: PromiseVoidFunction = async (): Promise => { + setIsLoading(true); + + const startAndEndDate: InBetween = + RangeStartAndEndDateTimeUtil.getStartAndEndDate( + props.dashboardStartAndEndDate, + ); + + if (!startAndEndDate.startValue || !startAndEndDate.endValue) { + setIsLoading(false); + setError("Please select a valid start and end date."); + return; + } + + try { + const query: Query = { + startTime: new InBetween( + startAndEndDate.startValue, + startAndEndDate.endValue, + ), + } as Query; + + // Add status filter if set + if ( + props.component.arguments.statusFilter && + props.component.arguments.statusFilter !== "" + ) { + (query as Record)["statusCode"] = parseInt( + props.component.arguments.statusFilter, + ); + } + + const listResult: ListResult = + await AnalyticsModelAPI.getList({ + modelType: Span, + query: query, + limit: maxRows, + skip: 0, + select: { + startTime: true, + name: true, + statusCode: true, + durationUnixNano: true, + traceId: true, + spanId: true, + kind: true, + serviceId: true, + }, + sort: { + startTime: SortOrder.Descending, + }, + requestOptions: {}, + }); + + setSpans(listResult.data); + setError(""); + } catch (err: unknown) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchTraces(); + }, [props.dashboardStartAndEndDate, props.refreshTick]); + + useEffect(() => { + fetchTraces(); + }, [ + props.component.arguments.statusFilter, + props.component.arguments.maxRows, + ]); + + if (isLoading) { + return ( +
+
+
+
+
+
+
+
+ {Array.from({ length: 5 }).map((_: unknown, i: number) => { + return ( +
+
+
+
+
+ ); + })} +
+
+ ); + } + + if (error) { + return ( +
+
+
+ +
+
+

{error}

+
+ ); + } + + return ( +
+ {props.component.arguments.title && ( +
+ + {props.component.arguments.title} + + + {spans.length} traces + +
+ )} +
+ + + + + + + + + + + {spans.map((span: Span, index: number) => { + const statusCode: number = + (span.statusCode as number) || SpanStatus.Unset; + const statusStyle: StatusStyle = getStatusStyle(statusCode); + const durationNano: number = + (span.durationUnixNano as number) || 0; + const startTime: Date | undefined = span.startTime + ? OneUptimeDate.fromString(span.startTime as string) + : undefined; + + return ( + + + + + + + ); + })} + {spans.length === 0 && ( + + + + )} + +
+ Span Name + + Duration + + Status + + Time +
+ {(span.name as string) || "—"} + + {formatDuration(durationNano)} + + + {statusStyle.label} + + + {startTime + ? OneUptimeDate.getDateAsLocalFormattedString( + startTime, + true, + ) + : "—"} +
+ No traces found +
+
+
+ ); +}; + +export default DashboardTraceListComponentElement; diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardValueComponent.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardValueComponent.tsx index 8e1d0fd6df..5c0c388ca2 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardValueComponent.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardValueComponent.tsx @@ -1,24 +1,89 @@ import React, { FunctionComponent, ReactElement, useEffect } from "react"; import { DashboardBaseComponentProps } from "./DashboardBaseComponent"; import AggregatedResult from "Common/Types/BaseDatabase/AggregatedResult"; -import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage"; +import AggregatedModel from "Common/Types/BaseDatabase/AggregatedModel"; import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; import MetricViewData from "Common/Types/Metrics/MetricViewData"; import MetricUtil from "../../Metrics/Utils/Metrics"; import API from "Common/UI/Utils/API/API"; -import DashboardValueComponent from "Common/Types/Dashboard/DashboardComponents/DashboardValueComponent"; +import DashboardValueComponentType from "Common/Types/Dashboard/DashboardComponents/DashboardValueComponent"; import AggregationType from "Common/Types/BaseDatabase/AggregationType"; import MetricQueryConfigData from "Common/Types/Metrics/MetricQueryConfigData"; import JSONFunctions from "Common/Types/JSONFunctions"; -import ComponentLoader from "Common/UI/Components/ComponentLoader/ComponentLoader"; import MetricType from "Common/Models/DatabaseModels/MetricType"; +import Icon from "Common/UI/Components/Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; import { RangeStartAndEndDateTimeUtil } from "Common/Types/Time/RangeStartAndEndDateTime"; export interface ComponentProps extends DashboardBaseComponentProps { - component: DashboardValueComponent; + component: DashboardValueComponentType; } -const DashboardValueComponent: FunctionComponent = ( +// Mini sparkline SVG component +interface SparklineProps { + data: Array; + width: number; + height: number; + color: string; + fillColor: string; +} + +const Sparkline: FunctionComponent = ( + sparklineProps: SparklineProps, +): ReactElement => { + if (sparklineProps.data.length < 2) { + return <>; + } + + const dataPoints: Array = sparklineProps.data; + const minVal: number = Math.min(...dataPoints); + const maxVal: number = Math.max(...dataPoints); + const range: number = maxVal - minVal || 1; + const padding: number = 2; + + const points: string = dataPoints + .map((value: number, index: number) => { + const x: number = + padding + + (index / (dataPoints.length - 1)) * + (sparklineProps.width - padding * 2); + const y: number = + sparklineProps.height - + padding - + ((value - minVal) / range) * (sparklineProps.height - padding * 2); + return `${x},${y}`; + }) + .join(" "); + + // Create fill area path + const firstX: number = padding; + const lastX: number = + padding + + ((dataPoints.length - 1) / (dataPoints.length - 1)) * + (sparklineProps.width - padding * 2); + const fillPoints: string = `${firstX},${sparklineProps.height} ${points} ${lastX},${sparklineProps.height}`; + + return ( + + + + + ); +}; + +const DashboardValueComponentElement: FunctionComponent = ( props: ComponentProps, ): ReactElement => { const [metricResults, setMetricResults] = React.useState< @@ -99,14 +164,9 @@ const DashboardValueComponent: FunctionComponent = ( useEffect(() => { fetchAggregatedResults(); - }, [props.dashboardStartAndEndDate, props.metricTypes]); + }, [props.dashboardStartAndEndDate, props.metricTypes, props.refreshTick]); useEffect(() => { - fetchAggregatedResults(); - }, []); - - useEffect(() => { - // set metricQueryConfig to the new value only if it is different from the previous value if ( JSONFunctions.isJSONObjectDifferent( metricQueryConfig || {}, @@ -119,39 +179,89 @@ const DashboardValueComponent: FunctionComponent = ( }, [props.component.arguments.metricQueryConfig]); if (isLoading) { - return ; + // Skeleton loading state + return ( +
+
+
+
+
+ ); } if (error) { - return ; + return ( +
+
+
+ +
+
+

{error}

+
+ ); } - let heightOfText: number | undefined = - (props.dashboardComponentHeightInPx || 0) - 100; + // Show setup state if no metric configured + if ( + !props.component.arguments.metricQueryConfig || + !props.component.arguments.metricQueryConfig.metricQueryData?.filterData || + Object.keys( + props.component.arguments.metricQueryConfig.metricQueryData.filterData, + ).length === 0 + ) { + return ( +
+
+ + + +
+

+ {props.component.arguments.title || "Value Widget"} +

+

+ Click to configure metric +

+
+ ); + } - if (heightOfText < 0) { - heightOfText = undefined; + // Collect all data points for sparkline and aggregation + const allDataPoints: Array = []; + for (const result of metricResults) { + for (const item of result.data) { + allDataPoints.push(item); + } } let aggregatedValue: number = 0; let avgCount: number = 0; - for (const result of metricResults) { - for (const item of result.data) { - const value: number = item.value; + for (const item of allDataPoints) { + const value: number = item.value; - if (aggregationType === AggregationType.Avg) { - aggregatedValue += value; - avgCount += 1; - } else if (aggregationType === AggregationType.Sum) { - aggregatedValue += value; - } else if (aggregationType === AggregationType.Min) { - aggregatedValue = Math.min(aggregatedValue, value); - } else if (aggregationType === AggregationType.Max) { - aggregatedValue = Math.max(aggregatedValue, value); - } else if (aggregationType === AggregationType.Count) { - aggregatedValue += 1; - } + if (aggregationType === AggregationType.Avg) { + aggregatedValue += value; + avgCount += 1; + } else if (aggregationType === AggregationType.Sum) { + aggregatedValue += value; + } else if (aggregationType === AggregationType.Min) { + aggregatedValue = Math.min(aggregatedValue, value); + } else if (aggregationType === AggregationType.Max) { + aggregatedValue = Math.max(aggregatedValue, value); + } else if (aggregationType === AggregationType.Count) { + aggregatedValue += 1; } } @@ -162,8 +272,17 @@ const DashboardValueComponent: FunctionComponent = ( // round to 2 decimal places aggregatedValue = Math.round(aggregatedValue * 100) / 100; - const valueHeightInPx: number = props.dashboardComponentHeightInPx * 0.4; - const titleHeightInPx: number = props.dashboardComponentHeightInPx * 0.13; + // Sparkline data - take raw values in order + const sparklineData: Array = allDataPoints.map( + (item: AggregatedModel) => { + return item.value; + }, + ); + + const valueHeightInPx: number = props.dashboardComponentHeightInPx * 0.35; + const titleHeightInPx: number = props.dashboardComponentHeightInPx * 0.11; + const showSparkline: boolean = + sparklineData.length >= 2 && props.dashboardComponentHeightInPx > 100; const unit: string | undefined = props.metricTypes?.find((item: MetricType) => { @@ -173,27 +292,144 @@ const DashboardValueComponent: FunctionComponent = ( ); })?.unit || ""; + // Determine color based on thresholds + let valueColorClass: string = "text-gray-900"; + let bgStyle: React.CSSProperties = {}; + let sparklineColor: string = "#6366f1"; // indigo + let sparklineFill: string = "rgba(99, 102, 241, 0.08)"; + const warningThreshold: number | undefined = + props.component.arguments.warningThreshold; + const criticalThreshold: number | undefined = + props.component.arguments.criticalThreshold; + + if ( + criticalThreshold !== undefined && + aggregatedValue >= criticalThreshold + ) { + valueColorClass = "text-red-600"; + bgStyle = { + background: + "linear-gradient(135deg, rgba(254, 226, 226, 0.4) 0%, rgba(254, 202, 202, 0.2) 100%)", + }; + sparklineColor = "#ef4444"; + sparklineFill = "rgba(239, 68, 68, 0.08)"; + } else if ( + warningThreshold !== undefined && + aggregatedValue >= warningThreshold + ) { + valueColorClass = "text-amber-600"; + bgStyle = { + background: + "linear-gradient(135deg, rgba(254, 243, 199, 0.4) 0%, rgba(253, 230, 138, 0.2) 100%)", + }; + sparklineColor = "#f59e0b"; + sparklineFill = "rgba(245, 158, 11, 0.08)"; + } + + // Calculate trend (compare first half avg to second half avg) + let trendPercent: number | null = null; + let trendDirection: "up" | "down" | "flat" = "flat"; + + if (sparklineData.length >= 4) { + const midpoint: number = Math.floor(sparklineData.length / 2); + const firstHalf: Array = sparklineData.slice(0, midpoint); + const secondHalf: Array = sparklineData.slice(midpoint); + const firstAvg: number = + firstHalf.reduce((a: number, b: number) => { + return a + b; + }, 0) / firstHalf.length; + const secondAvg: number = + secondHalf.reduce((a: number, b: number) => { + return a + b; + }, 0) / secondHalf.length; + + if (firstAvg !== 0) { + trendPercent = + Math.round(((secondAvg - firstAvg) / Math.abs(firstAvg)) * 1000) / 10; + trendDirection = + trendPercent > 0.5 ? "up" : trendPercent < -0.5 ? "down" : "flat"; + } + } + + const sparklineWidth: number = Math.min( + props.dashboardComponentWidthInPx * 0.6, + 120, + ); + const sparklineHeight: number = Math.min( + props.dashboardComponentHeightInPx * 0.18, + 30, + ); + return ( -
-
0 ? `${titleHeightInPx}px` : "", - }} - className="text-center text-bold mb-1 truncate" - > - {props.component.arguments.title || " "} +
+ {/* Title */} +
+ 0 + ? `${Math.max(Math.min(titleHeightInPx, 14), 11)}px` + : "12px", + }} + className="text-center font-medium text-gray-400 truncate uppercase tracking-wider" + > + {props.component.arguments.title || " "} +
+ + {/* Value */}
0 ? `${valueHeightInPx}px` : "", + lineHeight: 1.15, + letterSpacing: "-0.03em", }} > {aggregatedValue || "0"} - {unit} + 0 ? `${valueHeightInPx * 0.3}px` : "", + }} + > + {unit ? ` ${unit}` : ""} +
+ + {/* Trend indicator */} + {trendPercent !== null && trendDirection !== "flat" && ( +
+ {trendDirection === "up" ? "\u2191" : "\u2193"} + + {Math.abs(trendPercent)}% + +
+ )} + + {/* Sparkline */} + {showSparkline && ( +
+ +
+ )}
); }; -export default DashboardValueComponent; +export default DashboardValueComponentElement; diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/DashboardView.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/DashboardView.tsx index a8d5c0a607..1a40c9905f 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/DashboardView.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/DashboardView.tsx @@ -1,6 +1,7 @@ import React, { FunctionComponent, ReactElement, + useCallback, useEffect, useRef, useState, @@ -9,12 +10,19 @@ import DashboardToolbar from "./Toolbar/DashboardToolbar"; import DashboardCanvas from "./Canvas/Index"; import DashboardMode from "Common/Types/Dashboard/DashboardMode"; import DashboardComponentType from "Common/Types/Dashboard/DashboardComponentType"; -import DashboardViewConfig from "Common/Types/Dashboard/DashboardViewConfig"; +import DashboardViewConfig, { + AutoRefreshInterval, + getAutoRefreshIntervalInMs, +} from "Common/Types/Dashboard/DashboardViewConfig"; import { ObjectType } from "Common/Types/JSON"; import DashboardBaseComponent from "Common/Types/Dashboard/DashboardComponents/DashboardBaseComponent"; import DashboardChartComponentUtil from "Common/Utils/Dashboard/Components/DashboardChartComponent"; import DashboardValueComponentUtil from "Common/Utils/Dashboard/Components/DashboardValueComponent"; import DashboardTextComponentUtil from "Common/Utils/Dashboard/Components/DashboardTextComponent"; +import DashboardTableComponentUtil from "Common/Utils/Dashboard/Components/DashboardTableComponent"; +import DashboardGaugeComponentUtil from "Common/Utils/Dashboard/Components/DashboardGaugeComponent"; +import DashboardLogStreamComponentUtil from "Common/Utils/Dashboard/Components/DashboardLogStreamComponent"; +import DashboardTraceListComponentUtil from "Common/Utils/Dashboard/Components/DashboardTraceListComponent"; import BadDataException from "Common/Types/Exception/BadDataException"; import ObjectID from "Common/Types/ObjectID"; import Dashboard from "Common/Models/DatabaseModels/Dashboard"; @@ -30,6 +38,7 @@ import MetricUtil from "../Metrics/Utils/Metrics"; import RangeStartAndEndDateTime from "Common/Types/Time/RangeStartAndEndDateTime"; import TimeRange from "Common/Types/Time/TimeRange"; import MetricType from "Common/Models/DatabaseModels/MetricType"; +import DashboardVariable from "Common/Types/Dashboard/DashboardVariable"; export interface ComponentProps { dashboardId: ObjectID; @@ -49,6 +58,23 @@ const DashboardViewer: FunctionComponent = ( const [isSaving, setIsSaving] = useState(false); + // Auto-refresh state + const [autoRefreshInterval, setAutoRefreshInterval] = + useState(AutoRefreshInterval.OFF); + const [isRefreshing, setIsRefreshing] = useState(false); + const [dashboardVariables, setDashboardVariables] = useState< + Array + >([]); + + // Zoom stack for time range + const [timeRangeStack, setTimeRangeStack] = useState< + Array + >([]); + const autoRefreshTimerRef: React.MutableRefObject | null> = useRef | null>(null); + const [refreshTick, setRefreshTick] = useState(0); + // ref for dashboard div. const dashboardViewRef: React.RefObject = @@ -140,13 +166,23 @@ const DashboardViewer: FunctionComponent = ( return; } - setDashboardViewConfig( - JSONFunctions.deserializeValue( - dashboard.dashboardViewConfig || - DashboardViewConfigUtil.createDefaultDashboardViewConfig(), - ) as DashboardViewConfig, - ); + const config: DashboardViewConfig = JSONFunctions.deserializeValue( + dashboard.dashboardViewConfig || + DashboardViewConfigUtil.createDefaultDashboardViewConfig(), + ) as DashboardViewConfig; + + setDashboardViewConfig(config); setDashboardName(dashboard.name || "Untitled Dashboard"); + + // Restore saved auto-refresh interval + if (config.refreshInterval) { + setAutoRefreshInterval(config.refreshInterval); + } + + // Restore saved variables + if (config.variables) { + setDashboardVariables(config.variables); + } }; const loadPage: PromiseVoidFunction = async (): Promise => { @@ -169,6 +205,47 @@ const DashboardViewer: FunctionComponent = ( }); }, []); + // Auto-refresh timer management + const triggerRefresh: () => void = useCallback(() => { + setIsRefreshing(true); + setRefreshTick((prev: number) => { + return prev + 1; + }); + // Brief indicator + setTimeout(() => { + setIsRefreshing(false); + }, 500); + }, []); + + useEffect(() => { + // Clear existing timer + if (autoRefreshTimerRef.current) { + clearInterval(autoRefreshTimerRef.current); + autoRefreshTimerRef.current = null; + } + + // Don't auto-refresh in edit mode + if (dashboardMode === DashboardMode.Edit) { + return; + } + + const intervalMs: number | null = + getAutoRefreshIntervalInMs(autoRefreshInterval); + + if (intervalMs !== null) { + autoRefreshTimerRef.current = setInterval(() => { + triggerRefresh(); + }, intervalMs); + } + + return () => { + if (autoRefreshTimerRef.current) { + clearInterval(autoRefreshTimerRef.current); + autoRefreshTimerRef.current = null; + } + }; + }, [autoRefreshInterval, dashboardMode, triggerRefresh]); + const isEditMode: boolean = dashboardMode === DashboardMode.Edit; const sideBarWidth: number = isEditMode && selectedComponentId ? 650 : 0; @@ -191,9 +268,13 @@ const DashboardViewer: FunctionComponent = ( return (
= ( dashboardName={dashboardName} isSaving={isSaving} onSaveClick={() => { + // Save auto-refresh interval with the config + const configWithRefresh: DashboardViewConfig = { + ...dashboardViewConfig, + refreshInterval: autoRefreshInterval, + }; + setDashboardViewConfig(configWithRefresh); + saveDashboardViewConfig().catch((err: Error) => { setError(API.getFriendlyErrorMessage(err)); }); setDashboardMode(DashboardMode.View); }} startAndEndDate={startAndEndDate} + canResetZoom={timeRangeStack.length > 0} + onResetZoom={() => { + if (timeRangeStack.length > 0) { + const previousRange: RangeStartAndEndDateTime = + timeRangeStack[timeRangeStack.length - 1]!; + setStartAndEndDate(previousRange); + setTimeRangeStack(timeRangeStack.slice(0, -1)); + } + }} onStartAndEndDateChange={( newStartAndEndDate: RangeStartAndEndDateTime, ) => { + // Push current range to zoom stack before changing + setTimeRangeStack([...timeRangeStack, startAndEndDate]); setStartAndEndDate(newStartAndEndDate); }} onCancelEditClick={async () => { @@ -238,6 +337,26 @@ const DashboardViewer: FunctionComponent = ( onEditClick={() => { setDashboardMode(DashboardMode.Edit); }} + autoRefreshInterval={autoRefreshInterval} + onAutoRefreshIntervalChange={(interval: AutoRefreshInterval) => { + setAutoRefreshInterval(interval); + }} + isRefreshing={isRefreshing} + variables={dashboardVariables} + onVariableValueChange={(variableId: string, value: string) => { + const updatedVariables: Array = + dashboardVariables.map((v: DashboardVariable) => { + if (v.id === variableId) { + return { ...v, selectedValue: value }; + } + return v; + }); + setDashboardVariables(updatedVariables); + // Trigger refresh when variable changes + setRefreshTick((prev: number) => { + return prev + 1; + }); + }} onAddComponentClick={(componentType: DashboardComponentType) => { let newComponent: DashboardBaseComponent | null = null; @@ -253,6 +372,24 @@ const DashboardViewer: FunctionComponent = ( newComponent = DashboardTextComponentUtil.getDefaultComponent(); } + if (componentType === DashboardComponentType.Table) { + newComponent = DashboardTableComponentUtil.getDefaultComponent(); + } + + if (componentType === DashboardComponentType.Gauge) { + newComponent = DashboardGaugeComponentUtil.getDefaultComponent(); + } + + if (componentType === DashboardComponentType.LogStream) { + newComponent = + DashboardLogStreamComponentUtil.getDefaultComponent(); + } + + if (componentType === DashboardComponentType.TraceList) { + newComponent = + DashboardTraceListComponentUtil.getDefaultComponent(); + } + if (!newComponent) { throw new BadDataException( `Unknown component type: ${componentType}`, @@ -270,7 +407,7 @@ const DashboardViewer: FunctionComponent = ( setDashboardViewConfig(newDashboardConfig); }} /> -
+
{ @@ -291,6 +428,7 @@ const DashboardViewer: FunctionComponent = ( telemetryAttributes, metricTypes, }} + refreshTick={refreshTick} />
diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx index 96ba7d8660..5d893ce5f0 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx @@ -7,9 +7,14 @@ import MoreMenuItem from "Common/UI/Components/MoreMenu/MoreMenuItem"; import DashboardComponentType from "Common/Types/Dashboard/DashboardComponentType"; import RangeStartAndEndDateTime from "Common/Types/Time/RangeStartAndEndDateTime"; import RangeStartAndEndDateView from "Common/UI/Components/Date/RangeStartAndEndDateView"; -import DashboardViewConfig from "Common/Types/Dashboard/DashboardViewConfig"; +import DashboardViewConfig, { + AutoRefreshInterval, + getAutoRefreshIntervalLabel, +} from "Common/Types/Dashboard/DashboardViewConfig"; +import DashboardVariable from "Common/Types/Dashboard/DashboardVariable"; import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal"; import Loader from "Common/UI/Components/Loader/Loader"; +import DashboardVariableSelector from "./DashboardVariableSelector"; export interface ComponentProps { onEditClick: () => void; @@ -23,6 +28,13 @@ export interface ComponentProps { startAndEndDate: RangeStartAndEndDateTime; onStartAndEndDateChange: (startAndEndDate: RangeStartAndEndDateTime) => void; dashboardViewConfig: DashboardViewConfig; + autoRefreshInterval: AutoRefreshInterval; + onAutoRefreshIntervalChange: (interval: AutoRefreshInterval) => void; + isRefreshing?: boolean | undefined; + variables?: Array | undefined; + onVariableValueChange?: ((variableId: string, value: string) => void) | undefined; + canResetZoom?: boolean | undefined; + onResetZoom?: (() => void) | undefined; } const DashboardToolbar: FunctionComponent = ( @@ -34,105 +46,234 @@ const DashboardToolbar: FunctionComponent = ( const isSaving: boolean = props.isSaving; + const hasComponents: boolean = !!( + props.dashboardViewConfig && + props.dashboardViewConfig.components && + props.dashboardViewConfig.components.length > 0 + ); + return (
-
-
- {/* Name Component */} - {props.dashboardName} + {/* Accent top bar */} +
+ {/* Top row: Dashboard name + action buttons */} +
+
+

+ {props.dashboardName} +

+ {isEditMode && ( + + + Editing + + )} + {hasComponents && !isEditMode && ( + + {props.dashboardViewConfig.components.length} widget{props.dashboardViewConfig.components.length !== 1 ? "s" : ""} + + )} + {/* Refreshing indicator */} + {props.isRefreshing && + props.autoRefreshInterval !== AutoRefreshInterval.OFF && ( + + + Refreshing + + )}
+ {!isSaving && ( -
- {props.dashboardViewConfig && - props.dashboardViewConfig.components && - props.dashboardViewConfig.components.length > 0 && ( -
- { - props.onStartAndEndDateChange(startAndEndDate); +
+ {isEditMode ? ( + <> + + { + props.onAddComponentClick(DashboardComponentType.Chart); }} /> -
- )} + { + props.onAddComponentClick(DashboardComponentType.Value); + }} + /> + { + props.onAddComponentClick(DashboardComponentType.Text); + }} + /> + { + props.onAddComponentClick(DashboardComponentType.Table); + }} + /> + { + props.onAddComponentClick(DashboardComponentType.Gauge); + }} + /> + { + props.onAddComponentClick( + DashboardComponentType.LogStream, + ); + }} + /> + { + props.onAddComponentClick( + DashboardComponentType.TraceList, + ); + }} + /> + - {isEditMode ? ( - -
+ +
)} + {isSaving && ( -
+
-
Saving...
+ Saving...
)}
+ {/* Bottom row: Time range + variables (only when components exist and not in edit mode) */} + {hasComponents && !isEditMode && ( +
+
+ { + props.onStartAndEndDateChange(startAndEndDate); + }} + /> +
+ + {/* Template variables */} + {props.variables && + props.variables.length > 0 && + props.onVariableValueChange && ( + <> +
+ + + )} +
+ )} + {showCancelModal ? ( ; + onVariableValueChange: (variableId: string, value: string) => void; +} + +const DashboardVariableSelector: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + if (!props.variables || props.variables.length === 0) { + return <>; + } + + return ( +
+ {props.variables.map((variable: DashboardVariable) => { + const options: Array = variable.customListValues + ? variable.customListValues.split(",").map((v: string) => { + return v.trim(); + }) + : []; + + return ( +
+ + {options.length > 0 ? ( + + ) : ( + ) => { + props.onVariableValueChange(variable.id, e.target.value); + }} + /> + )} +
+ ); + })} +
+ ); +}; + +export default DashboardVariableSelector; diff --git a/App/FeatureSet/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx b/App/FeatureSet/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx index 8953bbe835..0b9a27270b 100644 --- a/App/FeatureSet/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx @@ -35,6 +35,13 @@ export interface ComponentProps { monitorStep: MonitorStep; } +const isMetricOnlyMonitorType = (monitorType: MonitorType): boolean => { + return ( + monitorType === MonitorType.Kubernetes || + monitorType === MonitorType.Metrics + ); +}; + const CriteriaFilterElement: FunctionComponent = ( props: ComponentProps, ): ReactElement => { @@ -77,6 +84,18 @@ const CriteriaFilterElement: FunctionComponent = ( ); }, [criteriaFilter]); + const isMetricOnly: boolean = isMetricOnlyMonitorType(props.monitorType); + + // Auto-select MetricValue for metric-only monitor types (Kubernetes, Metrics) + useEffect(() => { + if (isMetricOnly && criteriaFilter && criteriaFilter.checkOn !== CheckOn.MetricValue) { + props.onChange?.({ + ...criteriaFilter, + checkOn: CheckOn.MetricValue, + }); + } + }, [isMetricOnly]); + if (isLoading) { return <>; } @@ -125,15 +144,20 @@ const CriteriaFilterElement: FunctionComponent = ( ); }); + // Collect metric variables from both metricMonitor and kubernetesMonitor configs + const metricViewConfig = + props.monitorStep.data?.metricMonitor?.metricViewConfig || + props.monitorStep.data?.kubernetesMonitor?.metricViewConfig; + let metricVariables: Array = - props.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs?.map( + metricViewConfig?.queryConfigs?.map( (queryConfig: MetricQueryConfigData) => { return queryConfig.metricAliasData?.metricVariable || ""; }, ) || []; // push formula variables as well. - props.monitorStep.data?.metricMonitor?.metricViewConfig?.formulaConfigs?.forEach( + metricViewConfig?.formulaConfigs?.forEach( (formulaConfig: MetricFormulaConfigData) => { metricVariables.push(formulaConfig.metricAliasData.metricVariable || ""); }, @@ -168,6 +192,8 @@ const CriteriaFilterElement: FunctionComponent = ( return (
+ {/* Hide Filter Type dropdown for metric-only monitors since MetricValue is the only option */} + {!isMetricOnly && (
= ( }} />
+ )} {criteriaFilter?.checkOn && criteriaFilter?.checkOn === CheckOn.DiskUsagePercent && ( @@ -210,7 +237,10 @@ const CriteriaFilterElement: FunctionComponent = ( {criteriaFilter?.checkOn && criteriaFilter?.checkOn === CheckOn.MetricValue && (
- + = ( {criteriaFilter?.checkOn && criteriaFilter?.checkOn === CheckOn.MetricValue && (
- + = ( {!criteriaFilter?.checkOn || (criteriaFilter?.checkOn && (
- + = ( checkOn: criteriaFilter?.checkOn, }) && (
- + = (
{showCantDeleteModal ? ( { setShowCantDeleteModal(false); }} diff --git a/App/FeatureSet/Dashboard/src/Components/Form/Monitor/KubernetesMonitor/KubernetesMonitorStepForm.tsx b/App/FeatureSet/Dashboard/src/Components/Form/Monitor/KubernetesMonitor/KubernetesMonitorStepForm.tsx index 6ffd375261..62dea82c61 100644 --- a/App/FeatureSet/Dashboard/src/Components/Form/Monitor/KubernetesMonitor/KubernetesMonitorStepForm.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Form/Monitor/KubernetesMonitor/KubernetesMonitorStepForm.tsx @@ -221,14 +221,10 @@ const KubernetesMonitorStepForm: FunctionComponent = ( const clusterIdentifier: string = monitorStepKubernetesMonitor.clusterIdentifier; - if (!clusterIdentifier) { - // Just store the template selection, config will be built when cluster is selected - return; - } - // Get a dummy monitor step from the template to extract the kubernetes config + // Build even without a cluster so the metricViewConfig is populated for the METRIC dropdown const dummyStep: MonitorStep = template.getMonitorStep({ - clusterIdentifier, + clusterIdentifier: clusterIdentifier || "", onlineMonitorStatusId: ObjectID.generate(), offlineMonitorStatusId: ObjectID.generate(), defaultIncidentSeverityId: ObjectID.generate(), @@ -240,7 +236,7 @@ const KubernetesMonitorStepForm: FunctionComponent = ( if (dummyStep.data?.kubernetesMonitor) { props.onChange({ ...dummyStep.data.kubernetesMonitor, - clusterIdentifier, + clusterIdentifier: clusterIdentifier || "", }); } }; diff --git a/App/FeatureSet/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx b/App/FeatureSet/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx index 57289f3108..8701bc5bb8 100644 --- a/App/FeatureSet/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx @@ -247,8 +247,16 @@ const MonitorCriteriaInstanceElement: FunctionComponent = ( {/* Filters Section - Collapsible */} = (
= ( }); } - // Determine chart type - use BAR for bar chart type, AREA for everything else - const chartType: ChartType = - queryConfig.chartType === MetricChartType.BAR - ? ChartType.BAR - : ChartType.AREA; + let chartType: ChartType; + if (queryConfig.chartType === MetricChartType.BAR) { + chartType = ChartType.BAR; + } else if (queryConfig.chartType === MetricChartType.AREA) { + chartType = ChartType.AREA; + } else if (queryConfig.chartType === MetricChartType.LINE) { + chartType = ChartType.LINE; + } else { + chartType = ChartType.AREA; + } const chart: Chart = { id: index.toString(), diff --git a/App/FeatureSet/Dashboard/src/Components/Workspace/WorkspaceSummaryTable.tsx b/App/FeatureSet/Dashboard/src/Components/Workspace/WorkspaceSummaryTable.tsx index d4dd50f71c..cc6c0c26cc 100644 --- a/App/FeatureSet/Dashboard/src/Components/Workspace/WorkspaceSummaryTable.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Workspace/WorkspaceSummaryTable.tsx @@ -50,6 +50,7 @@ import { CustomElementProps } from "Common/UI/Components/Forms/Types/Field"; import OneUptimeDate from "Common/Types/Date"; import PageLoader from "Common/UI/Components/Loader/PageLoader"; import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage"; +import CheckboxElement from "Common/UI/Components/Checkbox/Checkbox"; export interface ComponentProps { workspaceType: WorkspaceType; @@ -322,13 +323,13 @@ const WorkspaceSummaryTable: FunctionComponent = ( }); } - // Default to all summary items if none selected + // Default to "All" if none selected if ( !values.summaryItems || (Array.isArray(values.summaryItems) && values.summaryItems.length === 0) ) { - values.summaryItems = allSummaryItems; + values.summaryItems = [WorkspaceNotificationSummaryItem.All]; } if (values.isEnabled === undefined || values.isEnabled === null) { @@ -493,18 +494,91 @@ const WorkspaceSummaryTable: FunctionComponent = ( }, title: "What to Include", description: - "Choose which sections appear in the summary. The report will be formatted with headers, statistics, and a detailed list.", - fieldType: FormFieldSchemaType.MultiSelectDropdown, - required: true, + "Choose which sections appear in the summary. Select \"All\" to include everything, or pick specific sections.", + fieldType: FormFieldSchemaType.CustomComponent, + required: false, stepId: "content", - dropdownOptions: allSummaryItems.map( - (item: WorkspaceNotificationSummaryItem) => { - return { - label: item, - value: item, - }; - }, - ), + getCustomElement: ( + value: FormValues, + elementProps: CustomElementProps, + ): ReactElement => { + const currentItems: Array = + (value.summaryItems as Array) || + [WorkspaceNotificationSummaryItem.All]; + + const isAllSelected: boolean = currentItems.includes( + WorkspaceNotificationSummaryItem.All, + ); + + const individualItems: Array = + allSummaryItems.filter( + (item: WorkspaceNotificationSummaryItem) => { + return item !== WorkspaceNotificationSummaryItem.All; + }, + ); + + return ( +
+ { + if (elementProps.onChange) { + if (checked) { + elementProps.onChange([ + WorkspaceNotificationSummaryItem.All, + ]); + } else { + elementProps.onChange([]); + } + } + }} + /> +
+ {individualItems.map( + (item: WorkspaceNotificationSummaryItem) => { + return ( + { + if (elementProps.onChange) { + let newItems: Array = + currentItems.filter( + (i: WorkspaceNotificationSummaryItem) => { + return ( + i !== + WorkspaceNotificationSummaryItem.All && + i !== item + ); + }, + ); + if (checked) { + newItems.push(item); + } + // If all individual items are selected, switch to "All" + if ( + newItems.length === individualItems.length + ) { + newItems = [ + WorkspaceNotificationSummaryItem.All, + ]; + } + elementProps.onChange(newItems); + } + }} + /> + ); + }, + )} +
+
+ ); + }, }, { field: { @@ -700,12 +774,6 @@ const WorkspaceSummaryTable: FunctionComponent = ( } submitButtonType={ButtonStyleType.NORMAL} submitButtonText={"Close"} - onClose={() => { - setShowTestSuccessModal(false); - setTestSummary(undefined); - setShowTestModal(false); - setTestError(""); - }} onSubmit={async () => { setShowTestSuccessModal(false); setTestSummary(undefined); diff --git a/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/AuthenticationSettings.tsx b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/AuthenticationSettings.tsx new file mode 100644 index 0000000000..4cffae8773 --- /dev/null +++ b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/AuthenticationSettings.tsx @@ -0,0 +1,160 @@ +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail"; +import FieldType from "Common/UI/Components/Types/FieldType"; +import Navigation from "Common/UI/Utils/Navigation"; +import Dashboard from "Common/Models/DatabaseModels/Dashboard"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; + +const DashboardAuthenticationSettings: FunctionComponent< + PageComponentProps +> = (): ReactElement => { + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + + return ( + + + name="Dashboard > Authentication Settings" + cardProps={{ + title: "Authentication Settings", + description: "Authentication settings for this dashboard.", + }} + editButtonText="Edit Settings" + isEditable={true} + formFields={[ + { + field: { + isPublicDashboard: true, + }, + title: "Is Visible to Public", + fieldType: FormFieldSchemaType.Toggle, + required: false, + placeholder: "Is this dashboard visible to public", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: Dashboard, + id: "model-detail-dashboard", + fields: [ + { + field: { + isPublicDashboard: true, + }, + fieldType: FieldType.Boolean, + title: "Is Visible to Public", + }, + ], + modelId: modelId, + }} + /> + + + name="Dashboard > Master Password" + cardProps={{ + title: "Master Password", + description: + "Rotate the password required to unlock a private dashboard. This value is stored as a secure hash and cannot be retrieved.", + }} + editButtonText="Update Master Password" + isEditable={true} + formFields={[ + { + field: { + enableMasterPassword: true, + }, + title: "Require Master Password", + fieldType: FormFieldSchemaType.Toggle, + required: false, + description: + "When enabled, visitors must enter the master password before viewing a private dashboard.", + }, + { + field: { + masterPassword: true, + }, + title: "Master Password", + fieldType: FormFieldSchemaType.Password, + required: false, + placeholder: "Enter a new master password", + description: + "Updating this value immediately replaces the existing master password.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: Dashboard, + id: "model-detail-dashboard-master-password", + fields: [ + { + field: { + enableMasterPassword: true, + }, + fieldType: FieldType.Boolean, + title: "Require Master Password", + placeholder: "No", + }, + { + title: "Master Password", + fieldType: FieldType.Element, + placeholder: "Hidden", + getElement: (): ReactElement => { + return ( +

+ For security reasons, the current master password is never + displayed. Use the update button to set a new password at + any time. +

+ ); + }, + }, + ], + modelId: modelId, + }} + /> + + + name="Dashboard > IP Whitelist" + cardProps={{ + title: "IP Whitelist", + description: + "IP Whitelist for this dashboard. If the dashboard is public then only IP addresses in this whitelist will be able to access the dashboard. If the dashboard is not public then only users who have access from the IP addresses in this whitelist will be able to access the dashboard.", + }} + editButtonText="Edit IP Whitelist" + isEditable={true} + formFields={[ + { + field: { + ipWhitelist: true, + }, + title: "IP Whitelist", + fieldType: FormFieldSchemaType.LongText, + required: false, + placeholder: + "Please enter the IP addresses or CIDR ranges to whitelist. One per line. This can be IPv4 or IPv6 addresses.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: Dashboard, + id: "model-detail-dashboard-ip-whitelist", + fields: [ + { + field: { + ipWhitelist: true, + }, + fieldType: FieldType.LongText, + title: "IP Whitelist", + placeholder: + "No IP addresses or CIDR ranges whitelisted. This will allow all IP addresses to access the dashboard.", + }, + ], + modelId: modelId, + }} + /> +
+ ); +}; + +export default DashboardAuthenticationSettings; diff --git a/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/CustomDomains.tsx b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/CustomDomains.tsx new file mode 100644 index 0000000000..11c45dff7a --- /dev/null +++ b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/CustomDomains.tsx @@ -0,0 +1,489 @@ +import PageComponentProps from "../../PageComponentProps"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "Common/UI/Components/Button/Button"; +import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal"; +import ModelTable from "Common/UI/Components/ModelTable/ModelTable"; +import FieldType from "Common/UI/Components/Types/FieldType"; +import { APP_API_URL, DashboardCNameRecord } from "Common/UI/Config"; +import API from "Common/UI/Utils/API/API"; +import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI"; +import Navigation from "Common/UI/Utils/Navigation"; +import Domain from "Common/Models/DatabaseModels/Domain"; +import DashboardDomain from "Common/Models/DatabaseModels/DashboardDomain"; +import React, { + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import OneUptimeDate from "Common/Types/Date"; +import FormValues from "Common/UI/Components/Forms/Types/FormValues"; +import ProjectUtil from "Common/UI/Utils/Project"; + +const DashboardCustomDomains: FunctionComponent = ( + props: PageComponentProps, +): ReactElement => { + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + + const [refreshToggle, setRefreshToggle] = useState( + OneUptimeDate.getCurrentDate().toString(), + ); + + const [showCnameModal, setShowCnameModal] = useState(false); + + const [selectedDashboardDomain, setSelectedDashboardDomain] = + useState(null); + + const [verifyCnameLoading, setVerifyCnameLoading] = + useState(false); + + const [orderSslLoading, setOrderSslLoading] = useState(false); + + const [error, setError] = useState(""); + + const [showOrderSSLModal, setShowOrderSSLModal] = + useState(false); + + return ( + + <> + + modelType={DashboardDomain} + query={{ + projectId: ProjectUtil.getCurrentProjectId()!, + dashboardId: modelId, + }} + name="Dashboard > Domains" + userPreferencesKey="dashboard-domains-table" + id="dashboard-domains-table" + isDeleteable={true} + isCreateable={true} + isEditable={true} + cardProps={{ + title: "Custom Domains", + description: `Important: Please add a CNAME record pointing to ${DashboardCNameRecord} for these domains for this to work.`, + }} + refreshToggle={refreshToggle} + onBeforeCreate={( + item: DashboardDomain, + ): Promise => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.dashboardId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + actionButtons={[ + { + title: "Add CNAME", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: DashboardDomain): boolean => { + if (item["isCnameVerified"]) { + return false; + } + + return true; + }, + onClick: async ( + item: DashboardDomain, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setShowCnameModal(true); + setSelectedDashboardDomain(item); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + { + title: "Order Free SSL", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: DashboardDomain): boolean => { + if ( + !item.isCustomCertificate && + item["isCnameVerified"] && + !item.isSslOrdered + ) { + return true; + } + + return false; + }, + onClick: async ( + item: DashboardDomain, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setShowOrderSSLModal(true); + setSelectedDashboardDomain(item); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + setSelectedDashboardDomain(null); + onError(err as Error); + } + }, + }, + ]} + noItemsMessage={"No custom domains found."} + viewPageRoute={Navigation.getCurrentRoute()} + selectMoreFields={{ + isSslOrdered: true, + isSslProvisioned: true, + isCnameVerified: true, + isCustomCertificate: true, + }} + formSteps={[ + { + title: "Basic", + id: "basic", + }, + { + title: "More", + id: "more", + }, + ]} + formFields={[ + { + field: { + subdomain: true, + }, + title: "Subdomain", + fieldType: FormFieldSchemaType.Text, + required: false, + placeholder: "dashboard (leave blank for root)", + description: + "Enter the subdomain label only (for example, dashboard). Leave blank or enter @ to use the root/apex domain.", + stepId: "basic", + disableSpellCheck: true, + }, + { + field: { + domain: true, + }, + title: "Domain", + description: + "Please select a verified domain from this list. If you do not see any domains in this list, please head over to More -> Project Settings -> Custom Domains to add one.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: Domain, + labelField: "domain", + valueField: "_id", + }, + required: true, + placeholder: "Select domain", + stepId: "basic", + }, + { + field: { + isCustomCertificate: true, + }, + title: "Upload Custom Certificate", + fieldType: FormFieldSchemaType.Toggle, + required: false, + defaultValue: false, + stepId: "more", + description: + "If you have a custom certificate, you can upload it here. If you do not have a certificate, we will order a free SSL certificate for you.", + }, + { + field: { + customCertificate: true, + }, + title: "Certificate", + fieldType: FormFieldSchemaType.LongText, + required: false, + stepId: "more", + placeholder: + "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----", + disableSpellCheck: true, + showIf: (item: FormValues): boolean => { + return Boolean(item.isCustomCertificate); + }, + }, + { + field: { + customCertificateKey: true, + }, + title: "Certificate Private Key", + fieldType: FormFieldSchemaType.LongText, + required: false, + placeholder: + "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", + stepId: "more", + disableSpellCheck: true, + showIf: (item: FormValues): boolean => { + return Boolean(item.isCustomCertificate); + }, + }, + ]} + showRefreshButton={true} + filters={[ + { + field: { + fullDomain: true, + }, + title: "Domain", + type: FieldType.Text, + }, + { + field: {}, + title: "CNAME Valid", + type: FieldType.Boolean, + }, + { + field: {}, + title: "SSL Provisioned", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + fullDomain: true, + }, + title: "Domain", + type: FieldType.Text, + }, + { + field: { + isCnameVerified: true, + }, + title: "Status", + type: FieldType.Element, + + getElement: (item: DashboardDomain): ReactElement => { + if (!item.isCnameVerified) { + return ( + + + Action Required: + {" "} + Please add your CNAME record. + + ); + } + + if (item.isCustomCertificate) { + return ( + + No action is required. Please allow 30 minutes for + the certificate to be provisioned. + + ); + } + + if (!item.isSslOrdered) { + return ( + + + Action Required: + {" "} + Please order SSL certificate. + + ); + } + + if (!item.isSslProvisioned) { + return ( + + No action is required. This SSL certificate will be + provisioned in 1 hour. If this does not happen. + Please contact support. + + ); + } + + return ( + + Certificate Provisioned. We will automatically renew + this certificate. No action required.{" "} + + ); + }, + }, + ]} + /> + + {selectedDashboardDomain?.fullDomain && showCnameModal && ( + + + Please add CNAME record to your domain. Details of + the CNAME records are: + +
+
+ + Record Type: CNAME + +
+ + Name: + {selectedDashboardDomain?.fullDomain} + +
+ + Content: + {DashboardCNameRecord} + +
+
+ + Once you have done this, it should take 24 hours to + automatically verify. + +
+ ) : ( +
+ + Custom Domains not enabled for this OneUptime + installation. Please contact your server admin to + enable this feature. To enable this feature, if you + are using Docker compose, the + DASHBOARD_CNAME_RECORD environment variable + must be set when starting the OneUptime cluster. If + you are using Helm and Kubernetes then set + dashboard.cnameRecord in the values.yaml file. + +
+ ) + } + submitButtonText={"Verify CNAME"} + onClose={() => { + setShowCnameModal(false); + setError(""); + return setSelectedDashboardDomain(null); + }} + isLoading={verifyCnameLoading} + error={error} + onSubmit={async () => { + try { + setVerifyCnameLoading(true); + setError(""); + + const response: + | HTTPResponse + | HTTPErrorResponse = + await API.get({ + url: URL.fromString( + APP_API_URL.toString(), + ).addRoute( + `/${ + new DashboardDomain().crudApiPath + }/verify-cname/${selectedDashboardDomain?.id?.toString()}`, + ), + data: {}, + headers: ModelAPI.getCommonHeaders(), + }); + + if (response.isFailure()) { + throw response; + } + + setShowCnameModal(false); + setRefreshToggle( + OneUptimeDate.getCurrentDate().toString(), + ); + setSelectedDashboardDomain(null); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setVerifyCnameLoading(false); + }} + /> + )} + + {showOrderSSLModal && selectedDashboardDomain && ( + + Please click on the button below to order SSL for this + domain. We will use LetsEncrypt to order a certificate. + This process is secure and completely free. The + certificate takes 3 hours to provision after its been + ordered. +
+ ) : ( +
+ + Custom Domains not enabled for this OneUptime + installation. Please contact your server admin to + enable this feature. + +
+ ) + } + submitButtonText={"Order Free SSL"} + onClose={() => { + setShowOrderSSLModal(false); + setError(""); + return setSelectedDashboardDomain(null); + }} + isLoading={orderSslLoading} + error={error} + onSubmit={async () => { + try { + setOrderSslLoading(true); + setError(""); + + const response: + | HTTPResponse + | HTTPErrorResponse = + await API.get({ + url: URL.fromString( + APP_API_URL.toString(), + ).addRoute( + `/${ + new DashboardDomain().crudApiPath + }/order-ssl/${selectedDashboardDomain?.id?.toString()}`, + ), + data: {}, + headers: ModelAPI.getCommonHeaders(), + }); + + if (response.isFailure()) { + throw response; + } + + setShowOrderSSLModal(false); + setRefreshToggle( + OneUptimeDate.getCurrentDate().toString(), + ); + setSelectedDashboardDomain(null); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setOrderSslLoading(false); + }} + /> + )} + + + ); +}; + +export default DashboardCustomDomains; diff --git a/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/DashboardPreviewLink.tsx b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/DashboardPreviewLink.tsx new file mode 100644 index 0000000000..0bf9380919 --- /dev/null +++ b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/DashboardPreviewLink.tsx @@ -0,0 +1,37 @@ +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "Common/UI/Components/Card/Card"; +import { PUBLIC_DASHBOARD_URL } from "Common/UI/Config"; +import React, { FunctionComponent, ReactElement } from "react"; +import Link from "Common/UI/Components/Link/Link"; + +export interface ComponentProps { + modelId: ObjectID; +} + +const DashboardPreviewLink: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + return ( + <> + + Here's a link to preview your public dashboard:{" "} + + {`${PUBLIC_DASHBOARD_URL.toString()}/${props.modelId}`} + + + } + /> + + ); +}; + +export default DashboardPreviewLink; diff --git a/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/Overview.tsx b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/Overview.tsx index dd41a01227..c79dba1871 100644 --- a/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/Overview.tsx +++ b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/Overview.tsx @@ -8,6 +8,7 @@ import Navigation from "Common/UI/Utils/Navigation"; import Label from "Common/Models/DatabaseModels/Label"; import Dashboard from "Common/Models/DatabaseModels/Dashboard"; import React, { Fragment, FunctionComponent, ReactElement } from "react"; +import DashboardPreviewLink from "./DashboardPreviewLink"; const DashboardView: FunctionComponent< PageComponentProps @@ -16,6 +17,7 @@ const DashboardView: FunctionComponent< return ( + {/* Dashboard View */} name="Dashboard > Dashboard Details" diff --git a/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/SideMenu.tsx b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/SideMenu.tsx index a50b1415d3..813937585e 100644 --- a/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/SideMenu.tsx +++ b/App/FeatureSet/Dashboard/src/Pages/Dashboards/View/SideMenu.tsx @@ -41,7 +41,33 @@ const DashboardSideMenu: FunctionComponent = ( /> + + + + + + = ( required: false, defaultValue: false, description: - "Show uptime percentage for the past 90 days beside this group on your status page.", + "Show uptime percentage beside this group on your status page. The number of days is configured in Status Page Settings.", stepId: "advanced", }, { diff --git a/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/Resources.tsx b/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/Resources.tsx index 5b57df1509..937f30ad95 100644 --- a/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/Resources.tsx +++ b/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/Resources.tsx @@ -218,7 +218,7 @@ const StatusPageDelete: FunctionComponent = ( required: false, defaultValue: false, description: - "Show uptime percentage for the past 90 days beside this resource on your status page.", + "Show uptime percentage beside this resource on your status page. The number of days is configured in Status Page Settings.", stepId: "advanced", }, { @@ -242,7 +242,7 @@ const StatusPageDelete: FunctionComponent = ( title: "Show Status History Chart", fieldType: FormFieldSchemaType.Toggle, required: false, - description: "Show resource status history for the past 90 days. ", + description: "Show resource status history chart. The number of days is configured in Status Page Settings.", defaultValue: true, stepId: "advanced", }, diff --git a/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx b/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx index aa32bc4d6d..232b398bde 100644 --- a/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx +++ b/App/FeatureSet/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx @@ -270,6 +270,47 @@ const StatusPageDelete: FunctionComponent< }} /> + + name="Status Page > Settings" + cardProps={{ + title: "Uptime History Settings", + description: + "Configure how many days of uptime history to show on the status page", + }} + editButtonText="Edit Settings" + isEditable={true} + formFields={[ + { + field: { + showUptimeHistoryInDays: true, + }, + title: "Show Uptime History (in days)", + fieldType: FormFieldSchemaType.Number, + required: true, + placeholder: "90", + validation: { + minValue: 1, + maxValue: 90, + }, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page-uptime-history", + fields: [ + { + field: { + showUptimeHistoryInDays: true, + }, + fieldType: FieldType.Number, + title: "Show Uptime History (in days)", + }, + ], + modelId: modelId, + }} + /> + name="Status Page > Settings" cardProps={{ diff --git a/App/FeatureSet/Dashboard/src/Pages/Workflow/View/Builder.tsx b/App/FeatureSet/Dashboard/src/Pages/Workflow/View/Builder.tsx index 006949b7a0..e467b79063 100644 --- a/App/FeatureSet/Dashboard/src/Pages/Workflow/View/Builder.tsx +++ b/App/FeatureSet/Dashboard/src/Pages/Workflow/View/Builder.tsx @@ -13,7 +13,6 @@ import ComponentMetadata, { NodeType, } from "Common/Types/Workflow/Component"; import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button"; -import Card from "Common/UI/Components/Card/Card"; import ComponentLoader from "Common/UI/Components/ComponentLoader/ComponentLoader"; import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal"; import { loadComponentsAndCategories } from "Common/UI/Components/Workflow/Utils"; @@ -231,11 +230,11 @@ const Delete: FunctionComponent = (): ReactElement => { }, }); - setSaveStatus("Changes Saved."); + setSaveStatus("Saved"); } catch (err) { setError(API.getFriendlyMessage(err)); - setSaveStatus("Save Error."); + setSaveStatus("Error saving"); } if (saveTimeout) { @@ -250,100 +249,146 @@ const Delete: FunctionComponent = (): ReactElement => { await loadGraph(); }, []); + type GetSaveStatusColorFunction = () => string; + + const getSaveStatusColor: GetSaveStatusColorFunction = (): string => { + if (saveStatus === "Saved") { + return "#10b981"; + } + if (saveStatus === "Error saving") { + return "#ef4444"; + } + return "#94a3b8"; + }; + return ( <> - -

{saveStatus}

-
-
-
-
-
-
-
- } + {/* Toolbar */} +
- {isLoading ? : <>} - - {!isLoading ? ( - { - setShowComponentPickerModal(value); +
+
{ - setShowRunModal(value); - }} - showRunModal={showRunModal} - initialEdges={edges} - onWorkflowUpdated={async ( - nodes: Array, - edges: Array, - ) => { - setNodes(nodes); - setEdges(edges); - await saveGraph(nodes, edges); - }} - onRun={async (component: NodeDataProp) => { - try { - const result: HTTPErrorResponse | HTTPResponse = - await API.post({ - url: URL.fromString(WORKFLOW_URL.toString()).addRoute( - "/manual/run/" + modelId.toString(), - ), - data: { - data: component.arguments, - }, - }); + > +
+ + {saveStatus || "Ready"} + +
+
- if (result instanceof HTTPErrorResponse) { - throw result; - } - - setShowRunSuccessConfirmation(true); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } +
+
+
+ + {/* Canvas */} + {isLoading ? ( +
+ +
+ ) : ( + { + setShowComponentPickerModal(value); + }} + initialNodes={nodes} + onRunModalUpdate={(value: boolean) => { + setShowRunModal(value); + }} + showRunModal={showRunModal} + initialEdges={edges} + onWorkflowUpdated={async ( + nodes: Array, + edges: Array, + ) => { + setNodes(nodes); + setEdges(edges); + await saveGraph(nodes, edges); + }} + onRun={async (component: NodeDataProp) => { + try { + const result: HTTPErrorResponse | HTTPResponse = + await API.post({ + url: URL.fromString(WORKFLOW_URL.toString()).addRoute( + "/manual/run/" + modelId.toString(), + ), + data: { + data: component.arguments, + }, + }); + + if (result instanceof HTTPErrorResponse) { + throw result; + } + + setShowRunSuccessConfirmation(true); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + }} + /> + )} + {error && ( = (): ReactElement => { {showRunSuccessConfirmation && ( { setShowRunSuccessConfirmation(false); }} diff --git a/App/FeatureSet/Dashboard/src/Routes/DashboardRoutes.tsx b/App/FeatureSet/Dashboard/src/Routes/DashboardRoutes.tsx index cd8ebbd5be..4218c872b1 100644 --- a/App/FeatureSet/Dashboard/src/Routes/DashboardRoutes.tsx +++ b/App/FeatureSet/Dashboard/src/Routes/DashboardRoutes.tsx @@ -16,6 +16,10 @@ import DashboardViewDelete from "../Pages/Dashboards/View/Delete"; import DashboardViewSettings from "../Pages/Dashboards/View/Settings"; +import DashboardViewAuthenticationSettings from "../Pages/Dashboards/View/AuthenticationSettings"; + +import DashboardViewCustomDomains from "../Pages/Dashboards/View/CustomDomains"; + const DashboardsRoutes: FunctionComponent = ( props: ComponentProps, ): ReactElement => { @@ -74,6 +78,36 @@ const DashboardsRoutes: FunctionComponent = ( /> } /> + + + } + /> + + + } + /> ); diff --git a/App/FeatureSet/Dashboard/src/Utils/PageMap.ts b/App/FeatureSet/Dashboard/src/Utils/PageMap.ts index b247ed6fb8..b464f5d1ad 100644 --- a/App/FeatureSet/Dashboard/src/Utils/PageMap.ts +++ b/App/FeatureSet/Dashboard/src/Utils/PageMap.ts @@ -268,6 +268,8 @@ enum PageMap { DASHBOARD_VIEW_OVERVIEW = "DASHBOARD_VIEW_OVERVIEW", DASHBOARD_VIEW_DELETE = "DASHBOARD_VIEW_DELETE", DASHBOARD_VIEW_SETTINGS = "DASHBOARD_VIEW_SETTINGS", + DASHBOARD_VIEW_AUTHENTICATION_SETTINGS = "DASHBOARD_VIEW_AUTHENTICATION_SETTINGS", + DASHBOARD_VIEW_CUSTOM_DOMAINS = "DASHBOARD_VIEW_CUSTOM_DOMAINS", STATUS_PAGES_ROOT = "STATUS_PAGES_ROOT", STATUS_PAGES = "STATUS_PAGES", diff --git a/App/FeatureSet/Dashboard/src/Utils/RouteMap.ts b/App/FeatureSet/Dashboard/src/Utils/RouteMap.ts index 72fb99d3bb..a721266c3d 100644 --- a/App/FeatureSet/Dashboard/src/Utils/RouteMap.ts +++ b/App/FeatureSet/Dashboard/src/Utils/RouteMap.ts @@ -148,6 +148,8 @@ export const DashboardsRoutePath: Dictionary = { [PageMap.DASHBOARD_VIEW_OVERVIEW]: `${RouteParams.ModelID}/overview`, [PageMap.DASHBOARD_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, [PageMap.DASHBOARD_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, + [PageMap.DASHBOARD_VIEW_AUTHENTICATION_SETTINGS]: `${RouteParams.ModelID}/authentication-settings`, + [PageMap.DASHBOARD_VIEW_CUSTOM_DOMAINS]: `${RouteParams.ModelID}/custom-domains`, }; export const StatusPagesRoutePath: Dictionary = { @@ -1754,6 +1756,18 @@ const RouteMap: Dictionary = { }`, ), + [PageMap.DASHBOARD_VIEW_AUTHENTICATION_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/dashboards/${ + DashboardsRoutePath[PageMap.DASHBOARD_VIEW_AUTHENTICATION_SETTINGS] + }`, + ), + + [PageMap.DASHBOARD_VIEW_CUSTOM_DOMAINS]: new Route( + `/dashboard/${RouteParams.ProjectID}/dashboards/${ + DashboardsRoutePath[PageMap.DASHBOARD_VIEW_CUSTOM_DOMAINS] + }`, + ), + // Status Pages [PageMap.STATUS_PAGES_ROOT]: new Route( diff --git a/App/FeatureSet/Frontend/Index.ts b/App/FeatureSet/Frontend/Index.ts index 56cb3e11f3..05c7ebeba3 100644 --- a/App/FeatureSet/Frontend/Index.ts +++ b/App/FeatureSet/Frontend/Index.ts @@ -24,6 +24,8 @@ import { StatusPageData, getStatusPageData, } from "./Utils/StatusPage"; +import DashboardDomainService from "Common/Server/Services/DashboardDomainService"; +import DashboardDomain from "Common/Models/DatabaseModels/DashboardDomain"; const app: ExpressApplication = Express.getExpressApp(); @@ -44,6 +46,11 @@ const StatusPagePublicPath: string = const StatusPageViewPath: string = "/usr/src/app/FeatureSet/StatusPage/views/index.ejs"; +const PublicDashboardPublicPath: string = + "/usr/src/app/FeatureSet/PublicDashboard/public"; +const PublicDashboardViewPath: string = + "/usr/src/app/FeatureSet/PublicDashboard/views/index.ejs"; + interface FrontendConfig { routePrefix: string; publicPath: string; @@ -67,6 +74,8 @@ const DashboardFallbackRoutePrefixesToSkip: Array = [ "/status-page-api", "/status-page-sso-api", "/status-page-identity-api", + "/public-dashboard", + "/public-dashboard-api", "/api", "/identity", "/notification", @@ -101,6 +110,7 @@ const StatusPageDomainFallbackRoutePrefixesToSkip: Array = [ "/status-page-api", "/status-page-sso-api", "/status-page-identity-api", + "/public-dashboard-api", "/.well-known", "/rss", ]; @@ -140,6 +150,12 @@ const DashboardFrontendConfig: FrontendConfig = { primaryHostOnly: true, }; +const PublicDashboardFrontendConfig: FrontendConfig = { + routePrefix: "/public-dashboard", + publicPath: PublicDashboardPublicPath, + indexViewPath: PublicDashboardViewPath, +}; + const DashboardRootPwaFileMap: Array<{ route: string; file: string }> = [ { route: "/manifest.json", file: "manifest.json" }, { route: "/sw.js", file: "sw.js" }, @@ -397,7 +413,32 @@ const registerFrontendApp: (frontendConfig: FrontendConfig) => void = ( ); }; -const registerStatusPageCustomDomainFallback: () => void = (): void => { +const isDashboardDomain: (hostname: string) => Promise = async ( + hostname: string, +): Promise => { + try { + const dashboardDomain: DashboardDomain | null = + await DashboardDomainService.findOneBy({ + query: { + fullDomain: hostname, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + return dashboardDomain !== null; + } catch (err) { + logger.error("Error checking if domain is a dashboard domain:"); + logger.error(err); + return false; + } +}; + +const registerCustomDomainFallback: () => void = (): void => { app.get( "*", async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { @@ -409,6 +450,20 @@ const registerStatusPageCustomDomainFallback: () => void = (): void => { return next(); } + // Check if this custom domain belongs to a PublicDashboard. + // If so, serve the PublicDashboard SPA instead of StatusPage. + const requestHostname: string = getRequestHostname(req); + + if (requestHostname && (await isDashboardDomain(requestHostname))) { + return renderFrontendIndexPage({ + req, + res, + next, + frontendConfig: PublicDashboardFrontendConfig, + }); + } + + // Default: serve StatusPage for custom domains return renderFrontendIndexPage({ req, res, @@ -483,8 +538,10 @@ const init: PromiseVoidFunction = async (): Promise => { registerFrontendApp(StatusPageFrontendConfig); + registerFrontendApp(PublicDashboardFrontendConfig); + registerDashboardRootPwaFiles(); - registerStatusPageCustomDomainFallback(); + registerCustomDomainFallback(); registerDashboardFallbackForPrimaryHost(); }; diff --git a/App/FeatureSet/Notification/API/BroadcastEmail.ts b/App/FeatureSet/Notification/API/BroadcastEmail.ts index 5d3b6503f2..eb7c714259 100644 --- a/App/FeatureSet/Notification/API/BroadcastEmail.ts +++ b/App/FeatureSet/Notification/API/BroadcastEmail.ts @@ -19,6 +19,79 @@ import User from "Common/Models/DatabaseModels/User"; const router: ExpressRouter = Express.getRouter(); +const BATCH_SIZE: number = 100; + +async function sendBroadcastEmailsInBackground(data: { + subject: string; + htmlMessage: string; +}): Promise { + let skip: number = 0; + let sentCount: number = 0; + let errorCount: number = 0; + let totalUsers: number = 0; + + try { + while (true) { + const users: Array = await UserService.findBy({ + query: {}, + select: { + email: true, + }, + skip: skip, + limit: BATCH_SIZE, + props: { + isRoot: true, + }, + }); + + if (users.length === 0) { + break; + } + + totalUsers += users.length; + + for (const user of users) { + if (!user.email) { + continue; + } + + try { + const mail: EmailMessage = { + templateType: EmailTemplateType.SimpleMessage, + toEmail: user.email, + subject: data.subject, + vars: { + subject: data.subject, + message: data.htmlMessage, + }, + body: "", + }; + + await MailService.send(mail); + sentCount++; + } catch (err) { + errorCount++; + logger.error( + `Failed to send broadcast email to ${user.email.toString()}: ${err}`, + ); + } + } + + if (users.length < BATCH_SIZE) { + break; + } + + skip += users.length; + } + + logger.info( + `Broadcast email completed. Total users: ${totalUsers}, Sent: ${sentCount}, Errors: ${errorCount}`, + ); + } catch (err) { + logger.error(`Broadcast email background job failed: ${err}`); + } +} + router.post( "/send-test", MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware, @@ -85,56 +158,24 @@ router.post( throw new BadDataException("Message is required"); } - const users: Array = await UserService.findAllBy({ - query: {}, - select: { - email: true, - }, - skip: 0, - props: { - isRoot: true, - }, - }); - const htmlMessage: string = await Markdown.convertToHTML( message, MarkdownContentType.Email, ); - let sentCount: number = 0; - let errorCount: number = 0; + // Send response immediately so the request doesn't timeout. + // Emails are sent in the background. + Response.sendJsonObjectResponse(req, res, { + message: + "Broadcast email job has been started. Emails will be sent in the background.", + }); - for (const user of users) { - if (!user.email) { - continue; - } - - try { - const mail: EmailMessage = { - templateType: EmailTemplateType.SimpleMessage, - toEmail: user.email, - subject: subject, - vars: { - subject: subject, - message: htmlMessage, - }, - body: "", - }; - - await MailService.send(mail); - sentCount++; - } catch (err) { - errorCount++; - logger.error( - `Failed to send broadcast email to ${user.email.toString()}: ${err}`, - ); - } - } - - return Response.sendJsonObjectResponse(req, res, { - totalUsers: users.length, - sentCount: sentCount, - errorCount: errorCount, + // Process emails in the background after the response is sent. + sendBroadcastEmailsInBackground({ + subject, + htmlMessage, + }).catch((err: Error) => { + logger.error(`Broadcast email background job failed: ${err}`); }); } catch (err) { return next(err); diff --git a/App/FeatureSet/PublicDashboard/Serve.ts b/App/FeatureSet/PublicDashboard/Serve.ts new file mode 100644 index 0000000000..ab49131dbe --- /dev/null +++ b/App/FeatureSet/PublicDashboard/Serve.ts @@ -0,0 +1,65 @@ +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Express, { + ExpressApplication, + ExpressRequest, + ExpressResponse, +} from "Common/Server/Utils/Express"; +import logger from "Common/Server/Utils/Logger"; +import App from "Common/Server/Utils/StartServer"; +import "ejs"; +import { + getPublicDashboardData, + PublicDashboardData, +} from "./src/Server/Utils/PublicDashboard"; + +export const APP_NAME: string = "public-dashboard"; + +const app: ExpressApplication = Express.getExpressApp(); + +const init: PromiseVoidFunction = async (): Promise => { + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: true, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + getVariablesToRenderIndexPage: async ( + req: ExpressRequest, + _res: ExpressResponse, + ) => { + const dashboardData: PublicDashboardData | null = + await getPublicDashboardData(req); + + if (dashboardData) { + return { + title: dashboardData.title, + description: dashboardData.description, + }; + } + return { + title: "Dashboard", + description: "View dashboard metrics and insights.", + }; + }, + }); + + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } +}; + +init().catch((err: Error) => { + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); +}); + +export default app; diff --git a/App/FeatureSet/PublicDashboard/esbuild.config.js b/App/FeatureSet/PublicDashboard/esbuild.config.js new file mode 100644 index 0000000000..0160a789d1 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/esbuild.config.js @@ -0,0 +1,12 @@ +const { createConfig, build, watch } = require('Common/UI/esbuild-config'); + +const config = createConfig({ + serviceName: 'PublicDashboard', + publicPath: '/public-dashboard/dist/', +}); + +if (process.argv.includes('--watch')) { + watch(config, 'PublicDashboard'); +} else { + build(config, 'PublicDashboard'); +} diff --git a/App/FeatureSet/PublicDashboard/index.d.ts b/App/FeatureSet/PublicDashboard/index.d.ts new file mode 100644 index 0000000000..6385e10bba --- /dev/null +++ b/App/FeatureSet/PublicDashboard/index.d.ts @@ -0,0 +1,30 @@ +declare module "*.png"; +declare module "*.svg"; +declare module "*.jpg"; +declare module "*.gif"; + +declare module "react-syntax-highlighter/dist/esm/prism-light"; +declare module "react-syntax-highlighter/dist/esm/styles/prism"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/javascript"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/typescript"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/jsx"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/tsx"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/python"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/bash"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/json"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/yaml"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/sql"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/go"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/java"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/css"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/markup"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/markdown"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/docker"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/rust"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/c"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/cpp"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/csharp"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/ruby"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/php"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/graphql"; +declare module "react-syntax-highlighter/dist/esm/languages/prism/http"; diff --git a/App/FeatureSet/PublicDashboard/nodemon.json b/App/FeatureSet/PublicDashboard/nodemon.json new file mode 100644 index 0000000000..39bf9e6ec1 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/nodemon.json @@ -0,0 +1,17 @@ +{ + "watch": ["./*","../../Common/UI", "../../Common/Types", "../../Common/Utils", "../../Common/Models"], + "ext": "ts,tsx", + "ignore": [ + "./node_modules/**", + "./public/**", + "./bin/**", + "./public/**", + "./public/dist/**", + "./build/*", + "./build/**", + "./build/dist/*", + "./build/dist/**", + "../../Common/Server/**" + ], + "exec": " npm run dev-build && npm run start" +} diff --git a/App/FeatureSet/PublicDashboard/package-lock.json b/App/FeatureSet/PublicDashboard/package-lock.json new file mode 100644 index 0000000000..a60b4be1e8 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/package-lock.json @@ -0,0 +1,995 @@ +{ + "name": "@oneuptime/public-dashboard", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@oneuptime/public-dashboard", + "version": "0.1.0", + "dependencies": { + "Common": "file:../../../Common", + "ejs": "^3.1.10", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.2", + "use-async-effect": "^2.2.6" + }, + "devDependencies": { + "@types/node": "^16.11.35", + "@types/react": "^18.2.38", + "@types/react-dom": "^18.0.4", + "@types/react-router-dom": "^5.3.3", + "nodemon": "^2.0.20", + "ts-node": "^10.9.1" + } + }, + "../../../Common": { + "name": "@oneuptime/common", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@asteasolutions/zod-to-openapi": "^7.3.2", + "@bull-board/express": "^5.21.4", + "@clickhouse/client": "^1.10.1", + "@elastic/elasticsearch": "^8.12.1", + "@hcaptcha/react-hcaptcha": "^1.14.0", + "@monaco-editor/react": "^4.4.6", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.206.0", + "@opentelemetry/context-zone": "^1.25.1", + "@opentelemetry/exporter-logs-otlp-http": "^0.207.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.207.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.207.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.207.0", + "@opentelemetry/id-generator-aws-xray": "^1.2.2", + "@opentelemetry/instrumentation": "^0.207.0", + "@opentelemetry/instrumentation-fetch": "^0.207.0", + "@opentelemetry/instrumentation-xml-http-request": "^0.207.0", + "@opentelemetry/resources": "^1.25.1", + "@opentelemetry/sdk-logs": "^0.207.0", + "@opentelemetry/sdk-metrics": "^1.25.1", + "@opentelemetry/sdk-node": "^0.207.0", + "@opentelemetry/sdk-trace-web": "^1.25.1", + "@opentelemetry/semantic-conventions": "^1.37.0", + "@remixicon/react": "^4.2.0", + "@simplewebauthn/server": "^13.2.2", + "@tippyjs/react": "^4.2.6", + "@types/archiver": "^6.0.3", + "@types/crypto-js": "^4.2.2", + "@types/dompurify": "^3.0.5", + "@types/multer": "^2.0.0", + "@types/qrcode": "^1.5.5", + "@types/react-highlight": "^0.12.8", + "@types/react-syntax-highlighter": "^15.5.13", + "@types/uuid": "^8.3.4", + "@types/web-push": "^3.6.4", + "acme-client": "^5.3.0", + "airtable": "^0.12.2", + "archiver": "^7.0.1", + "axios": "^1.12.0", + "botbuilder": "^4.23.3", + "bullmq": "^5.61.0", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "cron-parser": "^4.8.1", + "crypto-js": "^4.2.0", + "dompurify": "^3.3.2", + "dotenv": "^16.4.4", + "ejs": "^3.1.10", + "elkjs": "^0.10.0", + "esbuild": "^0.25.5", + "expo-server-sdk": "^3.15.0", + "express": "^4.21.1", + "formik": "^2.4.6", + "history": "^5.3.0", + "ioredis": "^5.3.2", + "isolated-vm": "^6.0.2", + "json2csv": "^5.0.7", + "json5": "^2.2.3", + "jsonwebtoken": "^9.0.0", + "jwt-decode": "^4.0.0", + "marked": "^12.0.2", + "mermaid": "^11.12.2", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "multer": "^2.1.1", + "node-cron": "^3.0.3", + "nodemailer": "^7.0.7", + "otpauth": "^9.3.1", + "pg": "^8.16.3", + "playwright": "^1.56.0", + "posthog-js": "^1.275.3", + "prop-types": "^15.8.1", + "qrcode": "^1.5.3", + "react": "^18.3.1", + "react-beautiful-dnd": "^13.1.1", + "react-big-calendar": "^1.19.4", + "react-color": "^2.19.3", + "react-dom": "^18.3.1", + "react-dropzone": "^14.2.2", + "react-error-boundary": "^4.0.13", + "react-highlight": "^0.15.0", + "react-markdown": "^9.0.0", + "react-router-dom": "^6.30.1", + "react-select": "^5.4.0", + "react-spinners": "^0.14.1", + "react-syntax-highlighter": "^16.0.0", + "react-toggle": "^4.1.3", + "reactflow": "^11.11.4", + "recharts": "^2.12.7", + "redis-semaphore": "^5.5.1", + "reflect-metadata": "^0.2.2", + "remark-gfm": "^4.0.0", + "slackify-markdown": "^4.4.0", + "slugify": "^1.6.5", + "socket.io": "^4.7.4", + "socket.io-client": "^4.7.5", + "stripe": "^10.17.0", + "tailwind-merge": "^2.6.0", + "tippy.js": "^6.3.7", + "twilio": "^4.22.0", + "typeorm": "^0.3.26", + "typeorm-extension": "^2.2.13", + "universal-cookie": "^7.2.1", + "use-async-effect": "^2.2.6", + "uuid": "^8.3.2", + "web-push": "^3.6.7", + "zod": "^3.25.76" + }, + "devDependencies": { + "@faker-js/faker": "^8.0.2", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^14.4.3", + "@types/cookie-parser": "^1.4.4", + "@types/cors": "^2.8.12", + "@types/ejs": "^3.1.1", + "@types/express": "^4.17.13", + "@types/jest": "^28.1.4", + "@types/json2csv": "^5.0.3", + "@types/jsonwebtoken": "^8.5.9", + "@types/node": "^17.0.45", + "@types/node-cron": "^3.0.7", + "@types/nodemailer": "^6.4.7", + "@types/react": "^18.2.38", + "@types/react-beautiful-dnd": "^13.1.2", + "@types/react-big-calendar": "^1.8.5", + "@types/react-color": "^3.0.6", + "@types/react-test-renderer": "^18.0.0", + "@types/react-toggle": "^4.0.3", + "jest": "^28.1.1", + "jest-environment-jsdom": "^29.7.0", + "jest-mock-extended": "^3.0.5", + "react-test-renderer": "^18.2.0", + "sass": "^1.89.2", + "ts-jest": "^28.0.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/Common": { + "resolved": "../../../Common", + "link": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-async-effect": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/use-async-effect/-/use-async-effect-2.2.7.tgz", + "integrity": "sha512-Vq94tKPyo/9Nok4LOapV0GoGgZPhbeDW/bP6bulLPV4+lIoftaBRBBbGjTbM+j5W1Bm2EkUHJgapeu5YnQvKEA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/App/FeatureSet/PublicDashboard/package.json b/App/FeatureSet/PublicDashboard/package.json new file mode 100644 index 0000000000..aaee5d1978 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/package.json @@ -0,0 +1,52 @@ +{ + "name": "@oneuptime/public-dashboard", + "version": "0.1.0", + "private": false, + "repository": { + "type": "git", + "url": "https://github.com/OneUptime/oneuptime" + }, + "scripts": { + "dev": "npx nodemon", + "build": "NODE_ENV=production node esbuild.config.js", + "dev-build": "NODE_ENV=development node esbuild.config.js", + "analyze": "analyze=true NODE_ENV=production node esbuild.config.js", + "test": "", + "compile": "tsc", + "clear-modules": "rm -rf node_modules && rm package-lock.json && npm install", + "start": "node --require ts-node/register Serve.ts", + "audit": "npm audit --audit-level=low", + "configure": "npx npm-force-resolutions || echo 'No package-lock.json file. Skipping force resolutions'", + "dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "dependencies": { + "Common": "file:../../../Common", + + "ejs": "^3.1.10", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.2", + "use-async-effect": "^2.2.6" + }, + "devDependencies": { + "@types/node": "^16.11.35", + "@types/react": "^18.2.38", + "@types/react-dom": "^18.0.4", + "@types/react-router-dom": "^5.3.3", + "nodemon": "^2.0.20", + + "ts-node": "^10.9.1" + } +} diff --git a/App/FeatureSet/PublicDashboard/public/assets/js/tailwind-3.4.5.js b/App/FeatureSet/PublicDashboard/public/assets/js/tailwind-3.4.5.js new file mode 100644 index 0000000000..ef3a2d207d --- /dev/null +++ b/App/FeatureSet/PublicDashboard/public/assets/js/tailwind-3.4.5.js @@ -0,0 +1,2 @@ +/*! For license information please see tailwind-3.4.5.js.LICENSE.txt */ +(()=>{var e,t,r,n,i=Object.create,o=Object.defineProperty,s=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,l=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,u=e=>o(e,"__esModule",{value:!0}),d=e=>{if("undefined"!=typeof require)return require(e);throw new Error('Dynamic require of "'+e+'" is not supported')},p=(e,t)=>()=>(e&&(t=e(e=0)),t),f=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),h=(e,t)=>{for(var r in u(e),t)o(e,r,{get:t[r],enumerable:!0})},m=e=>((e,t,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of a(t))!c.call(e,n)&&"default"!==n&&o(e,n,{get:()=>t[n],enumerable:!(r=s(t,n))||r.enumerable});return e})(u(o(null!=e?i(l(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e),g=p((()=>{e={platform:"",env:{},versions:{node:"14.17.6"}}})),y=p((()=>{g(),t=0,r={readFileSync:e=>self[e]||"",statSync:()=>({mtimeMs:t++}),promises:{readFile:e=>Promise.resolve(self[e]||"")}}})),b=f(((e,t)=>{g();var r=class{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError("`maxSize` must be a number greater than 0");if("number"==typeof e.maxAge&&0===e.maxAge)throw new TypeError("`maxAge` must be a number greater than 0");this.maxSize=e.maxSize,this.maxAge=e.maxAge||1/0,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_emitEvictions(e){if("function"==typeof this.onEviction)for(let[t,r]of e)this.onEviction(t,r.value)}_deleteIfExpired(e,t){return"number"==typeof t.expiry&&t.expiry<=Date.now()&&("function"==typeof this.onEviction&&this.onEviction(e,t.value),this.delete(e))}_getOrDeleteIfExpired(e,t){if(!1===this._deleteIfExpired(e,t))return t.value}_getItemValue(e,t){return t.expiry?this._getOrDeleteIfExpired(e,t):t.value}_peek(e,t){let r=t.get(e);return this._getItemValue(e,r)}_set(e,t){this.cache.set(e,t),this._size++,this._size>=this.maxSize&&(this._size=0,this._emitEvictions(this.oldCache),this.oldCache=this.cache,this.cache=new Map)}_moveToRecent(e,t){this.oldCache.delete(e),this._set(e,t)}*_entriesAscending(){for(let e of this.oldCache){let[t,r]=e;this.cache.has(t)||!1===this._deleteIfExpired(t,r)&&(yield e)}for(let e of this.cache){let[t,r]=e;!1===this._deleteIfExpired(t,r)&&(yield e)}}get(e){if(this.cache.has(e)){let t=this.cache.get(e);return this._getItemValue(e,t)}if(this.oldCache.has(e)){let t=this.oldCache.get(e);if(!1===this._deleteIfExpired(e,t))return this._moveToRecent(e,t),t.value}}set(e,t,{maxAge:r=(this.maxAge===1/0?void 0:Date.now()+this.maxAge)}={}){this.cache.has(e)?this.cache.set(e,{value:t,maxAge:r}):this._set(e,{value:t,expiry:r})}has(e){return this.cache.has(e)?!this._deleteIfExpired(e,this.cache.get(e)):!!this.oldCache.has(e)&&!this._deleteIfExpired(e,this.oldCache.get(e))}peek(e){return this.cache.has(e)?this._peek(e,this.cache):this.oldCache.has(e)?this._peek(e,this.oldCache):void 0}delete(e){let t=this.cache.delete(e);return t&&this._size--,this.oldCache.delete(e)||t}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}resize(e){if(!(e&&e>0))throw new TypeError("`maxSize` must be a number greater than 0");let t=[...this._entriesAscending()],r=t.length-e;r<0?(this.cache=new Map(t),this.oldCache=new Map,this._size=t.length):(r>0&&this._emitEvictions(t.slice(0,r)),this.oldCache=new Map(t.slice(r)),this.cache=new Map,this._size=0),this.maxSize=e}*keys(){for(let[e]of this)yield e}*values(){for(let[,e]of this)yield e}*[Symbol.iterator](){for(let e of this.cache){let[t,r]=e;!1===this._deleteIfExpired(t,r)&&(yield[t,r.value])}for(let e of this.oldCache){let[t,r]=e;this.cache.has(t)||!1===this._deleteIfExpired(t,r)&&(yield[t,r.value])}}*entriesDescending(){let e=[...this.cache];for(let t=e.length-1;t>=0;--t){let r=e[t],[n,i]=r;!1===this._deleteIfExpired(n,i)&&(yield[n,i.value])}e=[...this.oldCache];for(let t=e.length-1;t>=0;--t){let r=e[t],[n,i]=r;this.cache.has(n)||!1===this._deleteIfExpired(n,i)&&(yield[n,i.value])}}*entriesAscending(){for(let[e,t]of this._entriesAscending())yield[e,t.value]}get size(){if(!this._size)return this.oldCache.size;let e=0;for(let t of this.oldCache.keys())this.cache.has(t)||e++;return Math.min(this._size+e,this.maxSize)}};t.exports=r})),v=p((()=>{g(),n=e=>e&&e._hash}));function w(e){return n(e,{ignoreUnknown:!0})}var x=p((()=>{g(),v()}));function k(e){if("0"==(e=`${e}`))return"0";if(/^[+-]?(\d+|\d*\.\d+)(e[+-]?\d+)?(%|\w+)?$/.test(e))return e.replace(/^[+-]?/,(e=>"-"===e?"":"-"));let t=["var","calc","min","max","clamp"];for(let r of t)if(e.includes(`${r}(`))return`calc(${e} * -1)`}var S,C=p((()=>{g()})),A=p((()=>{g(),S=["preflight","container","accessibility","pointerEvents","visibility","position","inset","isolation","zIndex","order","gridColumn","gridColumnStart","gridColumnEnd","gridRow","gridRowStart","gridRowEnd","float","clear","margin","boxSizing","lineClamp","display","aspectRatio","size","height","maxHeight","minHeight","width","minWidth","maxWidth","flex","flexShrink","flexGrow","flexBasis","tableLayout","captionSide","borderCollapse","borderSpacing","transformOrigin","translate","rotate","skew","scale","transform","animation","cursor","touchAction","userSelect","resize","scrollSnapType","scrollSnapAlign","scrollSnapStop","scrollMargin","scrollPadding","listStylePosition","listStyleType","listStyleImage","appearance","columns","breakBefore","breakInside","breakAfter","gridAutoColumns","gridAutoFlow","gridAutoRows","gridTemplateColumns","gridTemplateRows","flexDirection","flexWrap","placeContent","placeItems","alignContent","alignItems","justifyContent","justifyItems","gap","space","divideWidth","divideStyle","divideColor","divideOpacity","placeSelf","alignSelf","justifySelf","overflow","overscrollBehavior","scrollBehavior","textOverflow","hyphens","whitespace","textWrap","wordBreak","borderRadius","borderWidth","borderStyle","borderColor","borderOpacity","backgroundColor","backgroundOpacity","backgroundImage","gradientColorStops","boxDecorationBreak","backgroundSize","backgroundAttachment","backgroundClip","backgroundPosition","backgroundRepeat","backgroundOrigin","fill","stroke","strokeWidth","objectFit","objectPosition","padding","textAlign","textIndent","verticalAlign","fontFamily","fontSize","fontWeight","textTransform","fontStyle","fontVariantNumeric","lineHeight","letterSpacing","textColor","textOpacity","textDecoration","textDecorationColor","textDecorationStyle","textDecorationThickness","textUnderlineOffset","fontSmoothing","placeholderColor","placeholderOpacity","caretColor","accentColor","opacity","backgroundBlendMode","mixBlendMode","boxShadow","boxShadowColor","outlineStyle","outlineWidth","outlineOffset","outlineColor","ringWidth","ringColor","ringOpacity","ringOffsetWidth","ringOffsetColor","blur","brightness","contrast","dropShadow","grayscale","hueRotate","invert","saturate","sepia","filter","backdropBlur","backdropBrightness","backdropContrast","backdropGrayscale","backdropHueRotate","backdropInvert","backdropOpacity","backdropSaturate","backdropSepia","backdropFilter","transitionProperty","transitionDelay","transitionDuration","transitionTimingFunction","willChange","contain","content","forcedColorAdjust"]})),O=p((()=>{g()})),E={};h(E,{default:()=>_});var _,T=p((()=>{g(),_=new Proxy({},{get:()=>String})}));function P(t,r,n){void 0!==e&&e.env.JEST_WORKER_ID||n&&I.has(n)||(n&&I.add(n),console.warn(""),r.forEach((e=>console.warn(t,"-",e))))}function j(e){return _.dim(e)}var I,D,$=p((()=>{g(),T(),I=new Set,D={info(e,t){P(_.bold(_.cyan("info")),...Array.isArray(e)?[e]:[t,e])},warn(e,t){["content-problems"].includes(e)||P(_.bold(_.yellow("warn")),...Array.isArray(e)?[e]:[t,e])},risk(e,t){P(_.bold(_.magenta("risk")),...Array.isArray(e)?[e]:[t,e])}}})),B={};function R({version:e,from:t,to:r}){D.warn(`${t}-color-renamed`,[`As of Tailwind CSS ${e}, \`${t}\` has been renamed to \`${r}\`.`,"Update your configuration file to silence this warning."])}h(B,{default:()=>M});var M,U=p((()=>{g(),$(),M={inherit:"inherit",current:"currentColor",transparent:"transparent",black:"#000",white:"#fff",slate:{50:"#f8fafc",100:"#f1f5f9",200:"#e2e8f0",300:"#cbd5e1",400:"#94a3b8",500:"#64748b",600:"#475569",700:"#334155",800:"#1e293b",900:"#0f172a",950:"#020617"},gray:{50:"#f9fafb",100:"#f3f4f6",200:"#e5e7eb",300:"#d1d5db",400:"#9ca3af",500:"#6b7280",600:"#4b5563",700:"#374151",800:"#1f2937",900:"#111827",950:"#030712"},zinc:{50:"#fafafa",100:"#f4f4f5",200:"#e4e4e7",300:"#d4d4d8",400:"#a1a1aa",500:"#71717a",600:"#52525b",700:"#3f3f46",800:"#27272a",900:"#18181b",950:"#09090b"},neutral:{50:"#fafafa",100:"#f5f5f5",200:"#e5e5e5",300:"#d4d4d4",400:"#a3a3a3",500:"#737373",600:"#525252",700:"#404040",800:"#262626",900:"#171717",950:"#0a0a0a"},stone:{50:"#fafaf9",100:"#f5f5f4",200:"#e7e5e4",300:"#d6d3d1",400:"#a8a29e",500:"#78716c",600:"#57534e",700:"#44403c",800:"#292524",900:"#1c1917",950:"#0c0a09"},red:{50:"#fef2f2",100:"#fee2e2",200:"#fecaca",300:"#fca5a5",400:"#f87171",500:"#ef4444",600:"#dc2626",700:"#b91c1c",800:"#991b1b",900:"#7f1d1d",950:"#450a0a"},orange:{50:"#fff7ed",100:"#ffedd5",200:"#fed7aa",300:"#fdba74",400:"#fb923c",500:"#f97316",600:"#ea580c",700:"#c2410c",800:"#9a3412",900:"#7c2d12",950:"#431407"},amber:{50:"#fffbeb",100:"#fef3c7",200:"#fde68a",300:"#fcd34d",400:"#fbbf24",500:"#f59e0b",600:"#d97706",700:"#b45309",800:"#92400e",900:"#78350f",950:"#451a03"},yellow:{50:"#fefce8",100:"#fef9c3",200:"#fef08a",300:"#fde047",400:"#facc15",500:"#eab308",600:"#ca8a04",700:"#a16207",800:"#854d0e",900:"#713f12",950:"#422006"},lime:{50:"#f7fee7",100:"#ecfccb",200:"#d9f99d",300:"#bef264",400:"#a3e635",500:"#84cc16",600:"#65a30d",700:"#4d7c0f",800:"#3f6212",900:"#365314",950:"#1a2e05"},green:{50:"#f0fdf4",100:"#dcfce7",200:"#bbf7d0",300:"#86efac",400:"#4ade80",500:"#22c55e",600:"#16a34a",700:"#15803d",800:"#166534",900:"#14532d",950:"#052e16"},emerald:{50:"#ecfdf5",100:"#d1fae5",200:"#a7f3d0",300:"#6ee7b7",400:"#34d399",500:"#10b981",600:"#059669",700:"#047857",800:"#065f46",900:"#064e3b",950:"#022c22"},teal:{50:"#f0fdfa",100:"#ccfbf1",200:"#99f6e4",300:"#5eead4",400:"#2dd4bf",500:"#14b8a6",600:"#0d9488",700:"#0f766e",800:"#115e59",900:"#134e4a",950:"#042f2e"},cyan:{50:"#ecfeff",100:"#cffafe",200:"#a5f3fc",300:"#67e8f9",400:"#22d3ee",500:"#06b6d4",600:"#0891b2",700:"#0e7490",800:"#155e75",900:"#164e63",950:"#083344"},sky:{50:"#f0f9ff",100:"#e0f2fe",200:"#bae6fd",300:"#7dd3fc",400:"#38bdf8",500:"#0ea5e9",600:"#0284c7",700:"#0369a1",800:"#075985",900:"#0c4a6e",950:"#082f49"},blue:{50:"#eff6ff",100:"#dbeafe",200:"#bfdbfe",300:"#93c5fd",400:"#60a5fa",500:"#3b82f6",600:"#2563eb",700:"#1d4ed8",800:"#1e40af",900:"#1e3a8a",950:"#172554"},indigo:{50:"#eef2ff",100:"#e0e7ff",200:"#c7d2fe",300:"#a5b4fc",400:"#818cf8",500:"#6366f1",600:"#4f46e5",700:"#4338ca",800:"#3730a3",900:"#312e81",950:"#1e1b4b"},violet:{50:"#f5f3ff",100:"#ede9fe",200:"#ddd6fe",300:"#c4b5fd",400:"#a78bfa",500:"#8b5cf6",600:"#7c3aed",700:"#6d28d9",800:"#5b21b6",900:"#4c1d95",950:"#2e1065"},purple:{50:"#faf5ff",100:"#f3e8ff",200:"#e9d5ff",300:"#d8b4fe",400:"#c084fc",500:"#a855f7",600:"#9333ea",700:"#7e22ce",800:"#6b21a8",900:"#581c87",950:"#3b0764"},fuchsia:{50:"#fdf4ff",100:"#fae8ff",200:"#f5d0fe",300:"#f0abfc",400:"#e879f9",500:"#d946ef",600:"#c026d3",700:"#a21caf",800:"#86198f",900:"#701a75",950:"#4a044e"},pink:{50:"#fdf2f8",100:"#fce7f3",200:"#fbcfe8",300:"#f9a8d4",400:"#f472b6",500:"#ec4899",600:"#db2777",700:"#be185d",800:"#9d174d",900:"#831843",950:"#500724"},rose:{50:"#fff1f2",100:"#ffe4e6",200:"#fecdd3",300:"#fda4af",400:"#fb7185",500:"#f43f5e",600:"#e11d48",700:"#be123c",800:"#9f1239",900:"#881337",950:"#4c0519"},get lightBlue(){return R({version:"v2.2",from:"lightBlue",to:"sky"}),this.sky},get warmGray(){return R({version:"v3.0",from:"warmGray",to:"stone"}),this.stone},get trueGray(){return R({version:"v3.0",from:"trueGray",to:"neutral"}),this.neutral},get coolGray(){return R({version:"v3.0",from:"coolGray",to:"gray"}),this.gray},get blueGray(){return R({version:"v3.0",from:"blueGray",to:"slate"}),this.slate}}}));function z(e,...t){for(let r of t){for(let t in r)e?.hasOwnProperty?.(t)||(e[t]=r[t]);for(let t of Object.getOwnPropertySymbols(r))e?.hasOwnProperty?.(t)||(e[t]=r[t])}return e}var L=p((()=>{g()}));function F(e){if(Array.isArray(e))return e;if(e.split("[").length-1!=e.split("]").length-1)throw new Error(`Path is invalid. Has unbalanced brackets: ${e}`);return e.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean)}var N=p((()=>{g()}));function V(e,t){return G.future.includes(t)?"all"===e.future||(e?.future?.[t]??q[t]??!1):!!G.experimental.includes(t)&&("all"===e.experimental||(e?.experimental?.[t]??q[t]??!1))}function W(e){return"all"===e.experimental?G.experimental:Object.keys(e?.experimental??{}).filter((t=>G.experimental.includes(t)&&e.experimental[t]))}var q,G,Y=p((()=>{g(),T(),$(),q={optimizeUniversalDefaults:!1,generalizedModifiers:!0,disableColorOpacityUtilitiesByDefault:!1,relativeContentPathsByDefault:!1},G={future:["hoverOnlyWhenSupported","respectDefaultRingColorOpacity","disableColorOpacityUtilitiesByDefault","relativeContentPathsByDefault"],experimental:["optimizeUniversalDefaults","generalizedModifiers"]}})),H=p((()=>{g(),Y(),$()}));function Q(e){if("[object Object]"!==Object.prototype.toString.call(e))return!1;let t=Object.getPrototypeOf(e);return null===t||null===Object.getPrototypeOf(t)}var J=p((()=>{g()}));function X(e){return Array.isArray(e)?e.map((e=>X(e))):"object"==typeof e&&null!==e?Object.fromEntries(Object.entries(e).map((([e,t])=>[e,X(t)]))):e}var K=p((()=>{g()}));function Z(e){return e.replace(/\\,/g,"\\2c ")}var ee,te=p((()=>{g()})),re=p((()=>{g(),ee={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}}));function ne(e,{loose:t=!1}={}){if("string"!=typeof e)return null;if("transparent"===(e=e.trim()))return{mode:"rgb",color:["0","0","0"],alpha:"0"};if(e in ee)return{mode:"rgb",color:ee[e].map((e=>e.toString()))};let r=e.replace(se,((e,t,r,n,i)=>["#",t,t,r,r,n,n,i?i+i:""].join(""))).match(oe);if(null!==r)return{mode:"rgb",color:[parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16)].map((e=>e.toString())),alpha:r[4]?(parseInt(r[4],16)/255).toString():void 0};let n=e.match(de)??e.match(pe);if(null===n)return null;let i=[n[2],n[3],n[4]].filter(Boolean).map((e=>e.toString()));return 2===i.length&&i[0].startsWith("var(")?{mode:n[1],color:[i[0]],alpha:i[1]}:!t&&3!==i.length||i.length<3&&!i.some((e=>/^var\(.*?\)$/.test(e)))?null:{mode:n[1],color:i,alpha:n[5]?.toString?.()}}function ie({mode:e,color:t,alpha:r}){let n=void 0!==r;return"rgba"===e||"hsla"===e?`${e}(${t.join(", ")}${n?`, ${r}`:""})`:`${e}(${t.join(" ")}${n?` / ${r}`:""})`}var oe,se,ae,le,ce,ue,de,pe,fe=p((()=>{g(),re(),oe=/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i,se=/^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i,ae=/(?:\d+|\d*\.\d+)%?/,le=/(?:\s*,\s*|\s+)/,ce=/\s*[,/]\s*/,ue=/var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/,de=new RegExp(`^(rgba?)\\(\\s*(${ae.source}|${ue.source})(?:${le.source}(${ae.source}|${ue.source}))?(?:${le.source}(${ae.source}|${ue.source}))?(?:${ce.source}(${ae.source}|${ue.source}))?\\s*\\)$`),pe=new RegExp(`^(hsla?)\\(\\s*((?:${ae.source})(?:deg|rad|grad|turn)?|${ue.source})(?:${le.source}(${ae.source}|${ue.source}))?(?:${le.source}(${ae.source}|${ue.source}))?(?:${ce.source}(${ae.source}|${ue.source}))?\\s*\\)$`)}));function he(e,t,r){if("function"==typeof e)return e({opacityValue:t});let n=ne(e,{loose:!0});return null===n?r:ie({...n,alpha:t})}function me({color:e,property:t,variable:r}){let n=[].concat(t);if("function"==typeof e)return{[r]:"1",...Object.fromEntries(n.map((t=>[t,e({opacityVariable:r,opacityValue:`var(${r})`})])))};let i=ne(e);return null===i||void 0!==i.alpha?Object.fromEntries(n.map((t=>[t,e]))):{[r]:"1",...Object.fromEntries(n.map((e=>[e,ie({...i,alpha:`var(${r})`})])))}}var ge=p((()=>{g(),fe()}));function ye(e,t){let r=[],n=[],i=0,o=!1;for(let s=0;s{g()}));function ve(e){return ye(e,",").map((e=>{let t=e.trim(),r={raw:t},n=t.split(ke),i=new Set;for(let e of n)Se.lastIndex=0,!i.has("KEYWORD")&&xe.has(e)?(r.keyword=e,i.add("KEYWORD")):Se.test(e)?i.has("X")?i.has("Y")?i.has("BLUR")?i.has("SPREAD")||(r.spread=e,i.add("SPREAD")):(r.blur=e,i.add("BLUR")):(r.y=e,i.add("Y")):(r.x=e,i.add("X")):r.color?(r.unknown||(r.unknown=[]),r.unknown.push(e)):r.color=e;return r.valid=void 0!==r.x&&void 0!==r.y,r}))}function we(e){return e.map((e=>e.valid?[e.keyword,e.x,e.y,e.blur,e.spread,e.color].filter(Boolean).join(" "):e.raw)).join(", ")}var xe,ke,Se,Ce=p((()=>{g(),be(),xe=new Set(["inset","inherit","initial","revert","unset"]),ke=/\ +(?![^(]*\))/g,Se=/^-?(\d+|\.\d+)(.*?)$/g}));function Ae(e){return Le.some((t=>new RegExp(`^${t}\\(.*\\)`).test(e)))}function Oe(e,t=null,r=!0){let n=t&&Fe.has(t.property);return e.startsWith("--")&&!n?`var(${e})`:e.includes("url(")?e.split(/(url\(.*?\))/g).filter(Boolean).map((e=>/^url\(.*?\)$/.test(e)?e:Oe(e,t,!1))).join(""):(e=e.replace(/([^\\])_+/g,((e,t)=>t+" ".repeat(e.length-1))).replace(/^_/g," ").replace(/\\_/g,"_"),r&&(e=e.trim()),e=function(e){let t=["theme"],r=["min-content","max-content","fit-content","safe-area-inset-top","safe-area-inset-right","safe-area-inset-bottom","safe-area-inset-left","titlebar-area-x","titlebar-area-y","titlebar-area-width","titlebar-area-height","keyboard-inset-top","keyboard-inset-right","keyboard-inset-bottom","keyboard-inset-left","keyboard-inset-width","keyboard-inset-height","radial-gradient","linear-gradient","conic-gradient","repeating-radial-gradient","repeating-linear-gradient","repeating-conic-gradient"];return e.replace(/(calc|min|max|clamp)\(.+\)/g,(e=>{let n="";function i(){let e=n.trimEnd();return e[e.length-1]}for(let o=0;oe[o+r]===t))},a=function(t){let r=1/0;for(let n of t){let t=e.indexOf(n,o);-1!==t&&ts(e)))){let e=r.find((e=>s(e)));n+=e,o+=e.length-1}else t.some((e=>s(e)))?n+=a([")"]):s("[")?n+=a(["]"]):["+","-","*","/"].includes(l)&&!["(","+","-","*","/",","].includes(i())?n+=` ${l} `:n+=l}return n.replace(/\s+/g," ")}))}(e),e)}function Ee(e){return e.startsWith("url(")}function _e(e){return!isNaN(Number(e))||Ae(e)}function Te(e){return e.endsWith("%")&&_e(e.slice(0,-1))||Ae(e)}function Pe(e){return"0"===e||new RegExp(`^[+-]?[0-9]*.?[0-9]+(?:[eE][+-]?[0-9]+)?${Ne}$`).test(e)||Ae(e)}function je(e){return Ve.has(e)}function Ie(e){let t=ve(Oe(e));for(let e of t)if(!e.valid)return!1;return!0}function De(e){let t=0;return!!ye(e,"_").every((e=>!!(e=Oe(e)).startsWith("var(")||null!==ne(e,{loose:!0})&&(t++,!0)))&&t>0}function $e(e){let t=0;return!!ye(e,",").every((e=>!!(e=Oe(e)).startsWith("var(")||!!(Ee(e)||function(e){e=Oe(e);for(let t of We)if(e.startsWith(`${t}(`))return!0;return!1}(e)||["element(","image(","cross-fade(","image-set("].some((t=>e.startsWith(t))))&&(t++,!0)))&&t>0}function Be(e){let t=0;return!!ye(e,"_").every((e=>!!(e=Oe(e)).startsWith("var(")||!!(qe.has(e)||Pe(e)||Te(e))&&(t++,!0)))&&t>0}function Re(e){let t=0;return!!ye(e,",").every((e=>!!(e=Oe(e)).startsWith("var(")||!(e.includes(" ")&&!/(['"])([^"']+)\1/g.test(e)||/^\d/g.test(e))&&(t++,!0)))&&t>0}function Me(e){return Ge.has(e)}function Ue(e){return Ye.has(e)}function ze(e){return He.has(e)}var Le,Fe,Ne,Ve,We,qe,Ge,Ye,He,Qe=p((()=>{g(),fe(),Ce(),be(),Le=["min","max","clamp","calc"],Fe=new Set(["scroll-timeline-name","timeline-scope","view-timeline-name","font-palette","anchor-name","anchor-scope","position-anchor","position-try-options","scroll-timeline","animation-timeline","view-timeline","position-try"]),Ne=`(?:${["cm","mm","Q","in","pc","pt","px","em","ex","ch","rem","lh","rlh","vw","vh","vmin","vmax","vb","vi","svw","svh","lvw","lvh","dvw","dvh","cqw","cqh","cqi","cqb","cqmin","cqmax"].join("|")})`,Ve=new Set(["thin","medium","thick"]),We=new Set(["conic-gradient","linear-gradient","radial-gradient","repeating-conic-gradient","repeating-linear-gradient","repeating-radial-gradient"]),qe=new Set(["center","top","right","bottom","left"]),Ge=new Set(["serif","sans-serif","monospace","cursive","fantasy","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","math","emoji","fangsong"]),Ye=new Set(["xx-small","x-small","small","medium","large","x-large","xx-large","xxx-large"]),He=new Set(["larger","smaller"])}));function Je(e){let t=["cover","contain"];return ye(e,",").every((e=>{let r=ye(e,"_").filter(Boolean);return!(1!==r.length||!t.includes(r[0]))||(1===r.length||2===r.length)&&r.every((e=>Pe(e)||Te(e)||"auto"===e))}))}var Xe=p((()=>{g(),Qe(),be()}));function Ke(e,t){e.walkClasses((e=>{e.value=t(e.value),e.raws&&e.raws.value&&(e.raws.value=Z(e.raws.value))}))}function Ze(e,t){if(!tt(e))return;let r=e.slice(1,-1);return t(r)?Oe(r):void 0}function et(e,t={},{validate:r=(()=>!0)}={}){let n=t.values?.[e];return void 0!==n?n:t.supportsNegativeValues&&e.startsWith("-")?function(e,t={},r){let n=t[e];if(void 0!==n)return k(n);if(tt(e)){let t=Ze(e,r);return void 0===t?void 0:k(t)}}(e.slice(1),t.values,r):Ze(e,r)}function tt(e){return e.startsWith("[")&&e.endsWith("]")}function rt(e){let t=e.lastIndexOf("/"),r=e.lastIndexOf("[",t),n=e.indexOf("]",t);return"]"===e[t-1]||"["===e[t+1]||-1!==r&&-1!==n&&r")){let t=e;return({opacityValue:e=1})=>t.replace(//g,e)}return e}function it(e){return Oe(e.slice(1,-1))}function ot(e,t={},{tailwindConfig:r={}}={}){if(void 0!==t.values?.[e])return nt(t.values?.[e]);let[n,i]=rt(e);if(void 0!==i){let e=t.values?.[n]??(tt(n)?n.slice(1,-1):void 0);return void 0===e?void 0:(e=nt(e),tt(i)?he(e,it(i)):void 0===r.theme?.opacity?.[i]?void 0:he(e,r.theme.opacity[i]))}return et(e,t,{validate:De})}function st(e,t={}){return t.values?.[e]}function at(e){return(t,r)=>et(t,r,{validate:e})}function lt(e,t,r,n){if(r.values&&t in r.values)for(let{type:i}of e??[]){let e=ut[i](t,r,{tailwindConfig:n});if(void 0!==e)return[e,i,null]}if(tt(t)){let e=t.slice(1,-1),[n,i]=function(e,t){let r=e.indexOf(":");return-1===r?[void 0,e]:[e.slice(0,r),e.slice(r+1)]}(e);if(/^[\w-_]+$/g.test(n)){if(void 0!==n&&!dt.includes(n))return[]}else i=e;if(i.length>0&&dt.includes(n))return[et(`[${i}]`,r),n,null]}let i=ct(e,t,r,n);for(let e of i)return e;return[]}function*ct(e,t,r,n){let i=V(n,"generalizedModifiers"),[o,s]=rt(t);if(i&&null!=r.modifiers&&("any"===r.modifiers||"object"==typeof r.modifiers&&(s&&tt(s)||s in r.modifiers))||(o=t,s=void 0),void 0!==s&&""===o&&(o="DEFAULT"),void 0!==s&&"object"==typeof r.modifiers){let e=r.modifiers?.[s]??null;null!==e?s=e:tt(s)&&(s=it(s))}for(let{type:t}of e??[]){let e=ut[t](o,r,{tailwindConfig:n});void 0!==e&&(yield[e,t,s??null])}}var ut,dt,pt=p((()=>{g(),te(),ge(),Qe(),C(),Xe(),Y(),ut={any:et,color:ot,url:at(Ee),image:at($e),length:at(Pe),percentage:at(Te),position:at(Be),lookup:st,"generic-name":at(Me),"family-name":at(Re),number:at(_e),"line-width":at(je),"absolute-size":at(Ue),"relative-size":at(ze),shadow:at(Ie),size:at(Je)},dt=Object.keys(ut)}));function ft(e){return"function"==typeof e?e({}):e}var ht=p((()=>{g()}));function mt(e){return"function"==typeof e}function gt(e,...t){let r=t.pop();for(let n of t)for(let t in n){let i=r(e[t],n[t]);void 0===i?Q(e[t])&&Q(n[t])?e[t]=gt({},e[t],n[t],r):e[t]=n[t]:e[t]=i}return e}function yt(e){return e.reduce(((e,{extend:t})=>gt(e,t,((e,t)=>void 0===e?[t]:Array.isArray(e)?[t,...e]:[t,e]))),{})}function bt(e){return{...e.reduce(((e,t)=>z(e,t)),{}),extend:yt(e)}}function vt(e,t){return Array.isArray(e)&&Q(e[0])?e.concat(t):Array.isArray(t)&&Q(t[0])&&Q(e)?[e,...t]:Array.isArray(t)?t:void 0}function wt({extend:e,...t}){return gt(t,e,((e,t)=>mt(e)||t.some(mt)?(r,n)=>gt({},...[e,...t].map((e=>function(e,...t){return mt(e)?e(...t):e}(e,r,n))),vt):gt({},e,...t,vt)))}function xt(e){let t=(r,n)=>{for(let n of function*(e){let t=F(e);if(0===t.length||(yield t,Array.isArray(e)))return;let r=e.match(/^(.*?)\s*\/\s*([^/]+)$/);if(null!==r){let[,e,t]=r,n=F(e);n.alpha=t,yield n}}(r)){let r=0,i=e;for(;null!=i&&r(r[n]=mt(e[n])?e[n](t,At):e[n],r)),{})}function kt(e){let t=[];return e.forEach((e=>{t=[...t,e];let r=e?.plugins??[];0!==r.length&&r.forEach((e=>{e.__isOptionsFunction&&(e=e()),t=[...t,...kt([e?.config??{}])]}))})),t}function St(e){return[...e].reduceRight(((e,t)=>mt(t)?t({corePlugins:e}):function(e,t){return void 0===e?t:Array.isArray(e)?e:[...new Set(t.filter((t=>!1!==e&&!1!==e[t])).concat(Object.keys(e).filter((t=>!1!==e[t]))))]}(t,e)),S)}function Ct(e){return[...e].reduceRight(((e,t)=>[...e,...t]),[])}var At,Ot=p((()=>{g(),C(),A(),O(),U(),L(),N(),H(),J(),K(),pt(),ge(),ht(),At={colors:M,negative:e=>Object.keys(e).filter((t=>"0"!==e[t])).reduce(((t,r)=>{let n=k(e[r]);return void 0!==n&&(t[`-${r}`]=n),t}),{}),breakpoints:e=>Object.keys(e).filter((t=>"string"==typeof e[t])).reduce(((t,r)=>({...t,[`screen-${r}`]:e[r]})),{})}})),Et=f(((e,t)=>{g(),t.exports={content:[],presets:[],darkMode:"media",theme:{accentColor:({theme:e})=>({...e("colors"),auto:"auto"}),animation:{none:"none",spin:"spin 1s linear infinite",ping:"ping 1s cubic-bezier(0, 0, 0.2, 1) infinite",pulse:"pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",bounce:"bounce 1s infinite"},aria:{busy:'busy="true"',checked:'checked="true"',disabled:'disabled="true"',expanded:'expanded="true"',hidden:'hidden="true"',pressed:'pressed="true"',readonly:'readonly="true"',required:'required="true"',selected:'selected="true"'},aspectRatio:{auto:"auto",square:"1 / 1",video:"16 / 9"},backdropBlur:({theme:e})=>e("blur"),backdropBrightness:({theme:e})=>e("brightness"),backdropContrast:({theme:e})=>e("contrast"),backdropGrayscale:({theme:e})=>e("grayscale"),backdropHueRotate:({theme:e})=>e("hueRotate"),backdropInvert:({theme:e})=>e("invert"),backdropOpacity:({theme:e})=>e("opacity"),backdropSaturate:({theme:e})=>e("saturate"),backdropSepia:({theme:e})=>e("sepia"),backgroundColor:({theme:e})=>e("colors"),backgroundImage:{none:"none","gradient-to-t":"linear-gradient(to top, var(--tw-gradient-stops))","gradient-to-tr":"linear-gradient(to top right, var(--tw-gradient-stops))","gradient-to-r":"linear-gradient(to right, var(--tw-gradient-stops))","gradient-to-br":"linear-gradient(to bottom right, var(--tw-gradient-stops))","gradient-to-b":"linear-gradient(to bottom, var(--tw-gradient-stops))","gradient-to-bl":"linear-gradient(to bottom left, var(--tw-gradient-stops))","gradient-to-l":"linear-gradient(to left, var(--tw-gradient-stops))","gradient-to-tl":"linear-gradient(to top left, var(--tw-gradient-stops))"},backgroundOpacity:({theme:e})=>e("opacity"),backgroundPosition:{bottom:"bottom",center:"center",left:"left","left-bottom":"left bottom","left-top":"left top",right:"right","right-bottom":"right bottom","right-top":"right top",top:"top"},backgroundSize:{auto:"auto",cover:"cover",contain:"contain"},blur:{0:"0",none:"",sm:"4px",DEFAULT:"8px",md:"12px",lg:"16px",xl:"24px","2xl":"40px","3xl":"64px"},borderColor:({theme:e})=>({...e("colors"),DEFAULT:e("colors.gray.200","currentColor")}),borderOpacity:({theme:e})=>e("opacity"),borderRadius:{none:"0px",sm:"0.125rem",DEFAULT:"0.25rem",md:"0.375rem",lg:"0.5rem",xl:"0.75rem","2xl":"1rem","3xl":"1.5rem",full:"9999px"},borderSpacing:({theme:e})=>({...e("spacing")}),borderWidth:{DEFAULT:"1px",0:"0px",2:"2px",4:"4px",8:"8px"},boxShadow:{sm:"0 1px 2px 0 rgb(0 0 0 / 0.05)",DEFAULT:"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",md:"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",lg:"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",xl:"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)","2xl":"0 25px 50px -12px rgb(0 0 0 / 0.25)",inner:"inset 0 2px 4px 0 rgb(0 0 0 / 0.05)",none:"none"},boxShadowColor:({theme:e})=>e("colors"),brightness:{0:"0",50:".5",75:".75",90:".9",95:".95",100:"1",105:"1.05",110:"1.1",125:"1.25",150:"1.5",200:"2"},caretColor:({theme:e})=>e("colors"),colors:({colors:e})=>({inherit:e.inherit,current:e.current,transparent:e.transparent,black:e.black,white:e.white,slate:e.slate,gray:e.gray,zinc:e.zinc,neutral:e.neutral,stone:e.stone,red:e.red,orange:e.orange,amber:e.amber,yellow:e.yellow,lime:e.lime,green:e.green,emerald:e.emerald,teal:e.teal,cyan:e.cyan,sky:e.sky,blue:e.blue,indigo:e.indigo,violet:e.violet,purple:e.purple,fuchsia:e.fuchsia,pink:e.pink,rose:e.rose}),columns:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12","3xs":"16rem","2xs":"18rem",xs:"20rem",sm:"24rem",md:"28rem",lg:"32rem",xl:"36rem","2xl":"42rem","3xl":"48rem","4xl":"56rem","5xl":"64rem","6xl":"72rem","7xl":"80rem"},container:{},content:{none:"none"},contrast:{0:"0",50:".5",75:".75",100:"1",125:"1.25",150:"1.5",200:"2"},cursor:{auto:"auto",default:"default",pointer:"pointer",wait:"wait",text:"text",move:"move",help:"help","not-allowed":"not-allowed",none:"none","context-menu":"context-menu",progress:"progress",cell:"cell",crosshair:"crosshair","vertical-text":"vertical-text",alias:"alias",copy:"copy","no-drop":"no-drop",grab:"grab",grabbing:"grabbing","all-scroll":"all-scroll","col-resize":"col-resize","row-resize":"row-resize","n-resize":"n-resize","e-resize":"e-resize","s-resize":"s-resize","w-resize":"w-resize","ne-resize":"ne-resize","nw-resize":"nw-resize","se-resize":"se-resize","sw-resize":"sw-resize","ew-resize":"ew-resize","ns-resize":"ns-resize","nesw-resize":"nesw-resize","nwse-resize":"nwse-resize","zoom-in":"zoom-in","zoom-out":"zoom-out"},divideColor:({theme:e})=>e("borderColor"),divideOpacity:({theme:e})=>e("borderOpacity"),divideWidth:({theme:e})=>e("borderWidth"),dropShadow:{sm:"0 1px 1px rgb(0 0 0 / 0.05)",DEFAULT:["0 1px 2px rgb(0 0 0 / 0.1)","0 1px 1px rgb(0 0 0 / 0.06)"],md:["0 4px 3px rgb(0 0 0 / 0.07)","0 2px 2px rgb(0 0 0 / 0.06)"],lg:["0 10px 8px rgb(0 0 0 / 0.04)","0 4px 3px rgb(0 0 0 / 0.1)"],xl:["0 20px 13px rgb(0 0 0 / 0.03)","0 8px 5px rgb(0 0 0 / 0.08)"],"2xl":"0 25px 25px rgb(0 0 0 / 0.15)",none:"0 0 #0000"},fill:({theme:e})=>({none:"none",...e("colors")}),flex:{1:"1 1 0%",auto:"1 1 auto",initial:"0 1 auto",none:"none"},flexBasis:({theme:e})=>({auto:"auto",...e("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%",full:"100%"}),flexGrow:{0:"0",DEFAULT:"1"},flexShrink:{0:"0",DEFAULT:"1"},fontFamily:{sans:["ui-sans-serif","system-ui","sans-serif",'"Apple Color Emoji"','"Segoe UI Emoji"','"Segoe UI Symbol"','"Noto Color Emoji"'],serif:["ui-serif","Georgia","Cambria",'"Times New Roman"',"Times","serif"],mono:["ui-monospace","SFMono-Regular","Menlo","Monaco","Consolas",'"Liberation Mono"','"Courier New"',"monospace"]},fontSize:{xs:["0.75rem",{lineHeight:"1rem"}],sm:["0.875rem",{lineHeight:"1.25rem"}],base:["1rem",{lineHeight:"1.5rem"}],lg:["1.125rem",{lineHeight:"1.75rem"}],xl:["1.25rem",{lineHeight:"1.75rem"}],"2xl":["1.5rem",{lineHeight:"2rem"}],"3xl":["1.875rem",{lineHeight:"2.25rem"}],"4xl":["2.25rem",{lineHeight:"2.5rem"}],"5xl":["3rem",{lineHeight:"1"}],"6xl":["3.75rem",{lineHeight:"1"}],"7xl":["4.5rem",{lineHeight:"1"}],"8xl":["6rem",{lineHeight:"1"}],"9xl":["8rem",{lineHeight:"1"}]},fontWeight:{thin:"100",extralight:"200",light:"300",normal:"400",medium:"500",semibold:"600",bold:"700",extrabold:"800",black:"900"},gap:({theme:e})=>e("spacing"),gradientColorStops:({theme:e})=>e("colors"),gradientColorStopPositions:{"0%":"0%","5%":"5%","10%":"10%","15%":"15%","20%":"20%","25%":"25%","30%":"30%","35%":"35%","40%":"40%","45%":"45%","50%":"50%","55%":"55%","60%":"60%","65%":"65%","70%":"70%","75%":"75%","80%":"80%","85%":"85%","90%":"90%","95%":"95%","100%":"100%"},grayscale:{0:"0",DEFAULT:"100%"},gridAutoColumns:{auto:"auto",min:"min-content",max:"max-content",fr:"minmax(0, 1fr)"},gridAutoRows:{auto:"auto",min:"min-content",max:"max-content",fr:"minmax(0, 1fr)"},gridColumn:{auto:"auto","span-1":"span 1 / span 1","span-2":"span 2 / span 2","span-3":"span 3 / span 3","span-4":"span 4 / span 4","span-5":"span 5 / span 5","span-6":"span 6 / span 6","span-7":"span 7 / span 7","span-8":"span 8 / span 8","span-9":"span 9 / span 9","span-10":"span 10 / span 10","span-11":"span 11 / span 11","span-12":"span 12 / span 12","span-full":"1 / -1"},gridColumnEnd:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridColumnStart:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridRow:{auto:"auto","span-1":"span 1 / span 1","span-2":"span 2 / span 2","span-3":"span 3 / span 3","span-4":"span 4 / span 4","span-5":"span 5 / span 5","span-6":"span 6 / span 6","span-7":"span 7 / span 7","span-8":"span 8 / span 8","span-9":"span 9 / span 9","span-10":"span 10 / span 10","span-11":"span 11 / span 11","span-12":"span 12 / span 12","span-full":"1 / -1"},gridRowEnd:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridRowStart:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridTemplateColumns:{none:"none",subgrid:"subgrid",1:"repeat(1, minmax(0, 1fr))",2:"repeat(2, minmax(0, 1fr))",3:"repeat(3, minmax(0, 1fr))",4:"repeat(4, minmax(0, 1fr))",5:"repeat(5, minmax(0, 1fr))",6:"repeat(6, minmax(0, 1fr))",7:"repeat(7, minmax(0, 1fr))",8:"repeat(8, minmax(0, 1fr))",9:"repeat(9, minmax(0, 1fr))",10:"repeat(10, minmax(0, 1fr))",11:"repeat(11, minmax(0, 1fr))",12:"repeat(12, minmax(0, 1fr))"},gridTemplateRows:{none:"none",subgrid:"subgrid",1:"repeat(1, minmax(0, 1fr))",2:"repeat(2, minmax(0, 1fr))",3:"repeat(3, minmax(0, 1fr))",4:"repeat(4, minmax(0, 1fr))",5:"repeat(5, minmax(0, 1fr))",6:"repeat(6, minmax(0, 1fr))",7:"repeat(7, minmax(0, 1fr))",8:"repeat(8, minmax(0, 1fr))",9:"repeat(9, minmax(0, 1fr))",10:"repeat(10, minmax(0, 1fr))",11:"repeat(11, minmax(0, 1fr))",12:"repeat(12, minmax(0, 1fr))"},height:({theme:e})=>({auto:"auto",...e("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%",full:"100%",screen:"100vh",svh:"100svh",lvh:"100lvh",dvh:"100dvh",min:"min-content",max:"max-content",fit:"fit-content"}),hueRotate:{0:"0deg",15:"15deg",30:"30deg",60:"60deg",90:"90deg",180:"180deg"},inset:({theme:e})=>({auto:"auto",...e("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%",full:"100%"}),invert:{0:"0",DEFAULT:"100%"},keyframes:{spin:{to:{transform:"rotate(360deg)"}},ping:{"75%, 100%":{transform:"scale(2)",opacity:"0"}},pulse:{"50%":{opacity:".5"}},bounce:{"0%, 100%":{transform:"translateY(-25%)",animationTimingFunction:"cubic-bezier(0.8,0,1,1)"},"50%":{transform:"none",animationTimingFunction:"cubic-bezier(0,0,0.2,1)"}}},letterSpacing:{tighter:"-0.05em",tight:"-0.025em",normal:"0em",wide:"0.025em",wider:"0.05em",widest:"0.1em"},lineHeight:{none:"1",tight:"1.25",snug:"1.375",normal:"1.5",relaxed:"1.625",loose:"2",3:".75rem",4:"1rem",5:"1.25rem",6:"1.5rem",7:"1.75rem",8:"2rem",9:"2.25rem",10:"2.5rem"},listStyleType:{none:"none",disc:"disc",decimal:"decimal"},listStyleImage:{none:"none"},margin:({theme:e})=>({auto:"auto",...e("spacing")}),lineClamp:{1:"1",2:"2",3:"3",4:"4",5:"5",6:"6"},maxHeight:({theme:e})=>({...e("spacing"),none:"none",full:"100%",screen:"100vh",svh:"100svh",lvh:"100lvh",dvh:"100dvh",min:"min-content",max:"max-content",fit:"fit-content"}),maxWidth:({theme:e,breakpoints:t})=>({...e("spacing"),none:"none",xs:"20rem",sm:"24rem",md:"28rem",lg:"32rem",xl:"36rem","2xl":"42rem","3xl":"48rem","4xl":"56rem","5xl":"64rem","6xl":"72rem","7xl":"80rem",full:"100%",min:"min-content",max:"max-content",fit:"fit-content",prose:"65ch",...t(e("screens"))}),minHeight:({theme:e})=>({...e("spacing"),full:"100%",screen:"100vh",svh:"100svh",lvh:"100lvh",dvh:"100dvh",min:"min-content",max:"max-content",fit:"fit-content"}),minWidth:({theme:e})=>({...e("spacing"),full:"100%",min:"min-content",max:"max-content",fit:"fit-content"}),objectPosition:{bottom:"bottom",center:"center",left:"left","left-bottom":"left bottom","left-top":"left top",right:"right","right-bottom":"right bottom","right-top":"right top",top:"top"},opacity:{0:"0",5:"0.05",10:"0.1",15:"0.15",20:"0.2",25:"0.25",30:"0.3",35:"0.35",40:"0.4",45:"0.45",50:"0.5",55:"0.55",60:"0.6",65:"0.65",70:"0.7",75:"0.75",80:"0.8",85:"0.85",90:"0.9",95:"0.95",100:"1"},order:{first:"-9999",last:"9999",none:"0",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12"},outlineColor:({theme:e})=>e("colors"),outlineOffset:{0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},outlineWidth:{0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},padding:({theme:e})=>e("spacing"),placeholderColor:({theme:e})=>e("colors"),placeholderOpacity:({theme:e})=>e("opacity"),ringColor:({theme:e})=>({DEFAULT:e("colors.blue.500","#3b82f6"),...e("colors")}),ringOffsetColor:({theme:e})=>e("colors"),ringOffsetWidth:{0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},ringOpacity:({theme:e})=>({DEFAULT:"0.5",...e("opacity")}),ringWidth:{DEFAULT:"3px",0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},rotate:{0:"0deg",1:"1deg",2:"2deg",3:"3deg",6:"6deg",12:"12deg",45:"45deg",90:"90deg",180:"180deg"},saturate:{0:"0",50:".5",100:"1",150:"1.5",200:"2"},scale:{0:"0",50:".5",75:".75",90:".9",95:".95",100:"1",105:"1.05",110:"1.1",125:"1.25",150:"1.5"},screens:{sm:"640px",md:"768px",lg:"1024px",xl:"1280px","2xl":"1536px"},scrollMargin:({theme:e})=>({...e("spacing")}),scrollPadding:({theme:e})=>e("spacing"),sepia:{0:"0",DEFAULT:"100%"},skew:{0:"0deg",1:"1deg",2:"2deg",3:"3deg",6:"6deg",12:"12deg"},space:({theme:e})=>({...e("spacing")}),spacing:{px:"1px",0:"0px",.5:"0.125rem",1:"0.25rem",1.5:"0.375rem",2:"0.5rem",2.5:"0.625rem",3:"0.75rem",3.5:"0.875rem",4:"1rem",5:"1.25rem",6:"1.5rem",7:"1.75rem",8:"2rem",9:"2.25rem",10:"2.5rem",11:"2.75rem",12:"3rem",14:"3.5rem",16:"4rem",20:"5rem",24:"6rem",28:"7rem",32:"8rem",36:"9rem",40:"10rem",44:"11rem",48:"12rem",52:"13rem",56:"14rem",60:"15rem",64:"16rem",72:"18rem",80:"20rem",96:"24rem"},stroke:({theme:e})=>({none:"none",...e("colors")}),strokeWidth:{0:"0",1:"1",2:"2"},supports:{},data:{},textColor:({theme:e})=>e("colors"),textDecorationColor:({theme:e})=>e("colors"),textDecorationThickness:{auto:"auto","from-font":"from-font",0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},textIndent:({theme:e})=>({...e("spacing")}),textOpacity:({theme:e})=>e("opacity"),textUnderlineOffset:{auto:"auto",0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},transformOrigin:{center:"center",top:"top","top-right":"top right",right:"right","bottom-right":"bottom right",bottom:"bottom","bottom-left":"bottom left",left:"left","top-left":"top left"},transitionDelay:{0:"0s",75:"75ms",100:"100ms",150:"150ms",200:"200ms",300:"300ms",500:"500ms",700:"700ms",1e3:"1000ms"},transitionDuration:{DEFAULT:"150ms",0:"0s",75:"75ms",100:"100ms",150:"150ms",200:"200ms",300:"300ms",500:"500ms",700:"700ms",1e3:"1000ms"},transitionProperty:{none:"none",all:"all",DEFAULT:"color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter",colors:"color, background-color, border-color, text-decoration-color, fill, stroke",opacity:"opacity",shadow:"box-shadow",transform:"transform"},transitionTimingFunction:{DEFAULT:"cubic-bezier(0.4, 0, 0.2, 1)",linear:"linear",in:"cubic-bezier(0.4, 0, 1, 1)",out:"cubic-bezier(0, 0, 0.2, 1)","in-out":"cubic-bezier(0.4, 0, 0.2, 1)"},translate:({theme:e})=>({...e("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%",full:"100%"}),size:({theme:e})=>({auto:"auto",...e("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%",full:"100%",min:"min-content",max:"max-content",fit:"fit-content"}),width:({theme:e})=>({auto:"auto",...e("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%",full:"100%",screen:"100vw",svw:"100svw",lvw:"100lvw",dvw:"100dvw",min:"min-content",max:"max-content",fit:"fit-content"}),willChange:{auto:"auto",scroll:"scroll-position",contents:"contents",transform:"transform"},zIndex:{auto:"auto",0:"0",10:"10",20:"20",30:"30",40:"40",50:"50"}},plugins:[]}}));function _t(e){let t=(e?.presets??[Tt.default]).slice().reverse().flatMap((e=>_t(e instanceof Function?e():e))),r={respectDefaultRingColorOpacity:{theme:{ringColor:({theme:e})=>({DEFAULT:"#3b82f67f",...e("colors")})}},disableColorOpacityUtilitiesByDefault:{corePlugins:{backgroundOpacity:!1,borderOpacity:!1,divideOpacity:!1,placeholderOpacity:!1,ringOpacity:!1,textOpacity:!1}}},n=Object.keys(r).filter((t=>V(e,t))).map((e=>r[e]));return[e,...n,...t]}var Tt,Pt=p((()=>{g(),Tt=m(Et()),Y()})),jt={};function It(...e){let[,...t]=_t(e[0]);return function(e){let t=[...kt(e),{prefix:"",important:!1,separator:":"}];return function(e){(()=>{if(e.purge||!e.content||!Array.isArray(e.content)&&("object"!=typeof e.content||null===e.content))return!1;if(Array.isArray(e.content))return e.content.every((e=>"string"==typeof e||!("string"!=typeof e?.raw||e?.extension&&"string"!=typeof e?.extension)));if("object"==typeof e.content&&null!==e.content){if(Object.keys(e.content).some((e=>!["files","relative","extract","transform"].includes(e))))return!1;if(Array.isArray(e.content.files)){if(!e.content.files.every((e=>"string"==typeof e||!("string"!=typeof e?.raw||e?.extension&&"string"!=typeof e?.extension))))return!1;if("object"==typeof e.content.extract){for(let t of Object.values(e.content.extract))if("function"!=typeof t)return!1}else if(void 0!==e.content.extract&&"function"!=typeof e.content.extract)return!1;if("object"==typeof e.content.transform){for(let t of Object.values(e.content.transform))if("function"!=typeof t)return!1}else if(void 0!==e.content.transform&&"function"!=typeof e.content.transform)return!1;if("boolean"!=typeof e.content.relative&&void 0!==e.content.relative)return!1}return!0}return!1})()||D.warn("purge-deprecation",["The `purge`/`content` options have changed in Tailwind CSS v3.0.","Update your configuration file to eliminate this warning.","https://tailwindcss.com/docs/upgrade-guide#configure-content-sources"]),e.safelist=(()=>{let{content:t,purge:r,safelist:n}=e;return Array.isArray(n)?n:Array.isArray(t?.safelist)?t.safelist:Array.isArray(r?.safelist)?r.safelist:Array.isArray(r?.options?.safelist)?r.options.safelist:[]})(),e.blocklist=(()=>{let{blocklist:t}=e;if(Array.isArray(t)){if(t.every((e=>"string"==typeof e)))return t;D.warn("blocklist-invalid",["The `blocklist` option must be an array of strings.","https://tailwindcss.com/docs/content-configuration#discarding-classes"])}return[]})(),"function"==typeof e.prefix?(D.warn("prefix-function",["As of Tailwind CSS v3.0, `prefix` cannot be a function.","Update `prefix` in your configuration to be a string to eliminate this warning.","https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function"]),e.prefix=""):e.prefix=e.prefix??"",e.content={relative:(()=>{let{content:t}=e;return t?.relative?t.relative:V(e,"relativeContentPathsByDefault")})(),files:(()=>{let{content:t,purge:r}=e;return Array.isArray(r)?r:Array.isArray(r?.content)?r.content:Array.isArray(t)?t:Array.isArray(t?.content)?t.content:Array.isArray(t?.files)?t.files:[]})(),extract:(()=>{let t=e.purge?.extract?e.purge.extract:e.content?.extract?e.content.extract:e.purge?.extract?.DEFAULT?e.purge.extract.DEFAULT:e.content?.extract?.DEFAULT?e.content.extract.DEFAULT:e.purge?.options?.extractors?e.purge.options.extractors:e.content?.options?.extractors?e.content.options.extractors:{},r={},n=e.purge?.options?.defaultExtractor?e.purge.options.defaultExtractor:e.content?.options?.defaultExtractor?e.content.options.defaultExtractor:void 0;if(void 0!==n&&(r.DEFAULT=n),"function"==typeof t)r.DEFAULT=t;else if(Array.isArray(t))for(let{extensions:e,extractor:n}of t??[])for(let t of e)r[t]=n;else"object"==typeof t&&null!==t&&Object.assign(r,t);return r})(),transform:(()=>{let t=e.purge?.transform?e.purge.transform:e.content?.transform?e.content.transform:e.purge?.transform?.DEFAULT?e.purge.transform.DEFAULT:e.content?.transform?.DEFAULT?e.content.transform.DEFAULT:{},r={};return"function"==typeof t?r.DEFAULT=t:"object"==typeof t&&null!==t&&Object.assign(r,t),r})()};for(let t of e.content.files)if("string"==typeof t&&/{([^,]*?)}/g.test(t)){D.warn("invalid-glob-braces",[`The glob pattern ${j(t)} in your Tailwind CSS configuration is invalid.`,`Update it to ${j(t.replace(/{([^,]*?)}/g,"$1"))} to silence this warning.`]);break}return e}(z({theme:xt(wt(bt(t.map((e=>e?.theme??{}))))),corePlugins:St(t.map((e=>e.corePlugins))),plugins:Ct(e.map((e=>e?.plugins??[])))},...t))}([...e,...t])}h(jt,{default:()=>It});var Dt=p((()=>{g(),Ot(),Pt()})),$t={};h($t,{default:()=>Bt});var Bt,Rt=p((()=>{g(),Bt={resolve:e=>e,extname:e=>"."+e.split(".").pop()}}));function Mt(e){return"object"==typeof e&&null!==e}function Ut(e){return"string"==typeof e||e instanceof String}var zt,Lt=p((()=>{g(),y(),Rt(),zt=["./tailwind.config.js","./tailwind.config.cjs","./tailwind.config.mjs","./tailwind.config.ts","./tailwind.config.cts","./tailwind.config.mts"]})),Ft={};h(Ft,{default:()=>Nt});var Nt,Vt,Wt,qt=p((()=>{g(),Nt={parse:e=>({href:e})}})),Gt=f((()=>{g()})),Yt=f(((e,t)=>{g();var r=(T(),E),n=Gt(),i=class extends Error{constructor(e,t,r,n,o,s){super(e),this.name="CssSyntaxError",this.reason=e,o&&(this.file=o),n&&(this.source=n),s&&(this.plugin=s),void 0!==t&&void 0!==r&&("number"==typeof t?(this.line=t,this.column=r):(this.line=t.line,this.column=t.column,this.endLine=r.line,this.endColumn=r.column)),this.setMessage(),Error.captureStackTrace&&Error.captureStackTrace(this,i)}setMessage(){this.message=this.plugin?this.plugin+": ":"",this.message+=this.file?this.file:"",void 0!==this.line&&(this.message+=":"+this.line+":"+this.column),this.message+=": "+this.reason}showSourceCode(e){if(!this.source)return"";let t=this.source;null==e&&(e=r.isColorSupported),n&&e&&(t=n(t));let i,o,s=t.split(/\r?\n/),a=Math.max(this.line-3,0),l=Math.min(this.line+2,s.length),c=String(l).length;if(e){let{bold:e,red:t,gray:n}=r.createColors(!0);i=r=>e(t(r)),o=e=>n(e)}else i=o=e=>e;return s.slice(a,l).map(((e,t)=>{let r=a+1+t,n=" "+(" "+r).slice(-c)+" | ";if(r===this.line){let t=o(n.replace(/\d/g," "))+e.slice(0,this.column-1).replace(/[^\t]/g," ");return i(">")+o(n)+e+"\n "+t+i("^")}return" "+o(n)+e})).join("\n")}toString(){let e=this.showSourceCode();return e&&(e="\n\n"+e+"\n"),this.name+": "+this.message+e}};t.exports=i,i.default=i})),Ht=f(((e,t)=>{g(),t.exports.isClean=Symbol("isClean"),t.exports.my=Symbol("my")})),Qt=f(((e,t)=>{g();var r={colon:": ",indent:" ",beforeDecl:"\n",beforeRule:"\n",beforeOpen:" ",beforeClose:"\n",beforeComment:"\n",after:"\n",emptyBody:"",commentLeft:" ",commentRight:" ",semicolon:!1},n=class{constructor(e){this.builder=e}stringify(e,t){if(!this[e.type])throw new Error("Unknown AST node type "+e.type+". Maybe you need to change PostCSS stringifier.");this[e.type](e,t)}document(e){this.body(e)}root(e){this.body(e),e.raws.after&&this.builder(e.raws.after)}comment(e){let t=this.raw(e,"left","commentLeft"),r=this.raw(e,"right","commentRight");this.builder("/*"+t+e.text+r+"*/",e)}decl(e,t){let r=this.raw(e,"between","colon"),n=e.prop+r+this.rawValue(e,"value");e.important&&(n+=e.raws.important||" !important"),t&&(n+=";"),this.builder(n,e)}rule(e){this.block(e,this.rawValue(e,"selector")),e.raws.ownSemicolon&&this.builder(e.raws.ownSemicolon,e,"end")}atrule(e,t){let r="@"+e.name,n=e.params?this.rawValue(e,"params"):"";if(void 0!==e.raws.afterName?r+=e.raws.afterName:n&&(r+=" "),e.nodes)this.block(e,r+n);else{let i=(e.raws.between||"")+(t?";":"");this.builder(r+n+i,e)}}body(e){let t=e.nodes.length-1;for(;t>0&&"comment"===e.nodes[t].type;)t-=1;let r=this.raw(e,"semicolon");for(let n=0;n{if(i=e.raws[t],void 0!==i)return!1}))}var a;return void 0===i&&(i=r[n]),s.rawCache[n]=i,i}rawSemicolon(e){let t;return e.walk((e=>{if(e.nodes&&e.nodes.length&&"decl"===e.last.type&&(t=e.raws.semicolon,void 0!==t))return!1})),t}rawEmptyBody(e){let t;return e.walk((e=>{if(e.nodes&&0===e.nodes.length&&(t=e.raws.after,void 0!==t))return!1})),t}rawIndent(e){if(e.raws.indent)return e.raws.indent;let t;return e.walk((r=>{let n=r.parent;if(n&&n!==e&&n.parent&&n.parent===e&&void 0!==r.raws.before){let e=r.raws.before.split("\n");return t=e[e.length-1],t=t.replace(/\S/g,""),!1}})),t}rawBeforeComment(e,t){let r;return e.walkComments((e=>{if(void 0!==e.raws.before)return r=e.raws.before,r.includes("\n")&&(r=r.replace(/[^\n]+$/,"")),!1})),void 0===r?r=this.raw(t,null,"beforeDecl"):r&&(r=r.replace(/\S/g,"")),r}rawBeforeDecl(e,t){let r;return e.walkDecls((e=>{if(void 0!==e.raws.before)return r=e.raws.before,r.includes("\n")&&(r=r.replace(/[^\n]+$/,"")),!1})),void 0===r?r=this.raw(t,null,"beforeRule"):r&&(r=r.replace(/\S/g,"")),r}rawBeforeRule(e){let t;return e.walk((r=>{if(r.nodes&&(r.parent!==e||e.first!==r)&&void 0!==r.raws.before)return t=r.raws.before,t.includes("\n")&&(t=t.replace(/[^\n]+$/,"")),!1})),t&&(t=t.replace(/\S/g,"")),t}rawBeforeClose(e){let t;return e.walk((e=>{if(e.nodes&&e.nodes.length>0&&void 0!==e.raws.after)return t=e.raws.after,t.includes("\n")&&(t=t.replace(/[^\n]+$/,"")),!1})),t&&(t=t.replace(/\S/g,"")),t}rawBeforeOpen(e){let t;return e.walk((e=>{if("decl"!==e.type&&(t=e.raws.between,void 0!==t))return!1})),t}rawColon(e){let t;return e.walkDecls((e=>{if(void 0!==e.raws.between)return t=e.raws.between.replace(/[^\s:]/g,""),!1})),t}beforeAfter(e,t){let r;r="decl"===e.type?this.raw(e,null,"beforeDecl"):"comment"===e.type?this.raw(e,null,"beforeComment"):"before"===t?this.raw(e,null,"beforeRule"):this.raw(e,null,"beforeClose");let n=e.parent,i=0;for(;n&&"root"!==n.type;)i+=1,n=n.parent;if(r.includes("\n")){let t=this.raw(e,null,"indent");if(t.length)for(let e=0;e{g();var r=Qt();function n(e,t){new r(t).stringify(e)}t.exports=n,n.default=n})),Xt=f(((e,t)=>{g();var{isClean:r,my:n}=Ht(),i=Yt(),o=Qt(),s=Jt();function a(e,t){let r=new e.constructor;for(let n in e){if(!Object.prototype.hasOwnProperty.call(e,n)||"proxyCache"===n)continue;let i=e[n],o=typeof i;"parent"===n&&"object"===o?t&&(r[n]=t):"source"===n?r[n]=i:Array.isArray(i)?r[n]=i.map((e=>a(e,r))):("object"===o&&null!==i&&(i=a(i)),r[n]=i)}return r}var l=class{constructor(e={}){this.raws={},this[r]=!1,this[n]=!0;for(let t in e)if("nodes"===t){this.nodes=[];for(let r of e[t])"function"==typeof r.clone?this.append(r.clone()):this.append(r)}else this[t]=e[t]}error(e,t={}){if(this.source){let{start:r,end:n}=this.rangeBy(t);return this.source.input.error(e,{line:r.line,column:r.column},{line:n.line,column:n.column},t)}return new i(e)}warn(e,t,r){let n={node:this};for(let e in r)n[e]=r[e];return e.warn(t,n)}remove(){return this.parent&&this.parent.removeChild(this),this.parent=void 0,this}toString(e=s){e.stringify&&(e=e.stringify);let t="";return e(this,(e=>{t+=e})),t}assign(e={}){for(let t in e)this[t]=e[t];return this}clone(e={}){let t=a(this);for(let r in e)t[r]=e[r];return t}cloneBefore(e={}){let t=this.clone(e);return this.parent.insertBefore(this,t),t}cloneAfter(e={}){let t=this.clone(e);return this.parent.insertAfter(this,t),t}replaceWith(...e){if(this.parent){let t=this,r=!1;for(let n of e)n===this?r=!0:r?(this.parent.insertAfter(t,n),t=n):this.parent.insertBefore(t,n);r||this.remove()}return this}next(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e+1]}prev(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e-1]}before(e){return this.parent.insertBefore(this,e),this}after(e){return this.parent.insertAfter(this,e),this}root(){let e=this;for(;e.parent&&"document"!==e.parent.type;)e=e.parent;return e}raw(e,t){return(new o).raw(this,e,t)}cleanRaws(e){delete this.raws.before,delete this.raws.after,e||delete this.raws.between}toJSON(e,t){let r={},n=null==t;t=t||new Map;let i=0;for(let e in this){if(!Object.prototype.hasOwnProperty.call(this,e)||"parent"===e||"proxyCache"===e)continue;let n=this[e];if(Array.isArray(n))r[e]=n.map((e=>"object"==typeof e&&e.toJSON?e.toJSON(null,t):e));else if("object"==typeof n&&n.toJSON)r[e]=n.toJSON(null,t);else if("source"===e){let o=t.get(n.input);null==o&&(o=i,t.set(n.input,i),i++),r[e]={inputId:o,start:n.start,end:n.end}}else r[e]=n}return n&&(r.inputs=[...t.keys()].map((e=>e.toJSON()))),r}positionInside(e){let t=this.toString(),r=this.source.start.column,n=this.source.start.line;for(let i=0;i(e[t]===r||(e[t]=r,("prop"===t||"value"===t||"name"===t||"params"===t||"important"===t||"text"===t)&&e.markDirty()),!0),get:(e,t)=>"proxyOf"===t?e:"root"===t?()=>e.root().toProxy():e[t]}}toProxy(){return this.proxyCache||(this.proxyCache=new Proxy(this,this.getProxyProcessor())),this.proxyCache}addToError(e){if(e.postcssNode=this,e.stack&&this.source&&/\n\s{4}at /.test(e.stack)){let t=this.source;e.stack=e.stack.replace(/\n\s{4}at /,`$&${t.input.from}:${t.start.line}:${t.start.column}$&`)}return e}markDirty(){if(this[r]){this[r]=!1;let e=this;for(;e=e.parent;)e[r]=!1}}get proxyOf(){return this}};t.exports=l,l.default=l})),Kt=f(((e,t)=>{g();var r=Xt(),n=class extends r{constructor(e){e&&void 0!==e.value&&"string"!=typeof e.value&&(e={...e,value:String(e.value)}),super(e),this.type="decl"}get variable(){return this.prop.startsWith("--")||"$"===this.prop[0]}};t.exports=n,n.default=n})),Zt=f(((e,t)=>{g(),t.exports=function(e,t){return{generate:()=>{let r="";return e(t,(e=>{r+=e})),[r]}}}})),er=f(((e,t)=>{g();var r=Xt(),n=class extends r{constructor(e){super(e),this.type="comment"}};t.exports=n,n.default=n})),tr=f(((e,t)=>{g();var r,n,i,o,{isClean:s,my:a}=Ht(),l=Kt(),c=er(),u=Xt();function d(e){return e.map((e=>(e.nodes&&(e.nodes=d(e.nodes)),delete e.source,e)))}function p(e){if(e[s]=!1,e.proxyOf.nodes)for(let t of e.proxyOf.nodes)p(t)}var f=class extends u{push(e){return e.parent=this,this.proxyOf.nodes.push(e),this}each(e){if(!this.proxyOf.nodes)return;let t,r,n=this.getIterator();for(;this.indexes[n]{let n;try{n=e(t,r)}catch(e){throw t.addToError(e)}return!1!==n&&t.walk&&(n=t.walk(e)),n}))}walkDecls(e,t){return t?e instanceof RegExp?this.walk(((r,n)=>{if("decl"===r.type&&e.test(r.prop))return t(r,n)})):this.walk(((r,n)=>{if("decl"===r.type&&r.prop===e)return t(r,n)})):(t=e,this.walk(((e,r)=>{if("decl"===e.type)return t(e,r)})))}walkRules(e,t){return t?e instanceof RegExp?this.walk(((r,n)=>{if("rule"===r.type&&e.test(r.selector))return t(r,n)})):this.walk(((r,n)=>{if("rule"===r.type&&r.selector===e)return t(r,n)})):(t=e,this.walk(((e,r)=>{if("rule"===e.type)return t(e,r)})))}walkAtRules(e,t){return t?e instanceof RegExp?this.walk(((r,n)=>{if("atrule"===r.type&&e.test(r.name))return t(r,n)})):this.walk(((r,n)=>{if("atrule"===r.type&&r.name===e)return t(r,n)})):(t=e,this.walk(((e,r)=>{if("atrule"===e.type)return t(e,r)})))}walkComments(e){return this.walk(((t,r)=>{if("comment"===t.type)return e(t,r)}))}append(...e){for(let t of e){let e=this.normalize(t,this.last);for(let t of e)this.proxyOf.nodes.push(t)}return this.markDirty(),this}prepend(...e){e=e.reverse();for(let t of e){let e=this.normalize(t,this.first,"prepend").reverse();for(let t of e)this.proxyOf.nodes.unshift(t);for(let t in this.indexes)this.indexes[t]=this.indexes[t]+e.length}return this.markDirty(),this}cleanRaws(e){if(super.cleanRaws(e),this.nodes)for(let t of this.nodes)t.cleanRaws(e)}insertBefore(e,t){let r,n=this.index(e),i=0===n&&"prepend",o=this.normalize(t,this.proxyOf.nodes[n],i).reverse();n=this.index(e);for(let e of o)this.proxyOf.nodes.splice(n,0,e);for(let e in this.indexes)r=this.indexes[e],n<=r&&(this.indexes[e]=r+o.length);return this.markDirty(),this}insertAfter(e,t){let r,n=this.index(e),i=this.normalize(t,this.proxyOf.nodes[n]).reverse();n=this.index(e);for(let e of i)this.proxyOf.nodes.splice(n+1,0,e);for(let e in this.indexes)r=this.indexes[e],n=e&&(this.indexes[r]=t-1);return this.markDirty(),this}removeAll(){for(let e of this.proxyOf.nodes)e.parent=void 0;return this.proxyOf.nodes=[],this.markDirty(),this}replaceValues(e,t,r){return r||(r=t,t={}),this.walkDecls((n=>{t.props&&!t.props.includes(n.prop)||t.fast&&!n.value.includes(t.fast)||(n.value=n.value.replace(e,r))})),this.markDirty(),this}every(e){return this.nodes.every(e)}some(e){return this.nodes.some(e)}index(e){return"number"==typeof e?e:(e.proxyOf&&(e=e.proxyOf),this.proxyOf.nodes.indexOf(e))}get first(){if(this.proxyOf.nodes)return this.proxyOf.nodes[0]}get last(){if(this.proxyOf.nodes)return this.proxyOf.nodes[this.proxyOf.nodes.length-1]}normalize(e,t){if("string"==typeof e)e=d(r(e).nodes);else if(Array.isArray(e)){e=e.slice(0);for(let t of e)t.parent&&t.parent.removeChild(t,"ignore")}else if("root"===e.type&&"document"!==this.type){e=e.nodes.slice(0);for(let t of e)t.parent&&t.parent.removeChild(t,"ignore")}else if(e.type)e=[e];else if(e.prop){if(void 0===e.value)throw new Error("Value field is missed in node creation");"string"!=typeof e.value&&(e.value=String(e.value)),e=[new l(e)]}else if(e.selector)e=[new n(e)];else if(e.name)e=[new i(e)];else{if(!e.text)throw new Error("Unknown node type in node creation");e=[new c(e)]}return e.map((e=>(e[a]||f.rebuild(e),(e=e.proxyOf).parent&&e.parent.removeChild(e),e[s]&&p(e),void 0===e.raws.before&&t&&void 0!==t.raws.before&&(e.raws.before=t.raws.before.replace(/\S/g,"")),e.parent=this.proxyOf,e)))}getProxyProcessor(){return{set:(e,t,r)=>(e[t]===r||(e[t]=r,("name"===t||"params"===t||"selector"===t)&&e.markDirty()),!0),get:(e,t)=>"proxyOf"===t?e:e[t]?"each"===t||"string"==typeof t&&t.startsWith("walk")?(...r)=>e[t](...r.map((e=>"function"==typeof e?(t,r)=>e(t.toProxy(),r):e))):"every"===t||"some"===t?r=>e[t](((e,...t)=>r(e.toProxy(),...t))):"root"===t?()=>e.root().toProxy():"nodes"===t?e.nodes.map((e=>e.toProxy())):"first"===t||"last"===t?e[t].toProxy():e[t]:e[t]}}getIterator(){this.lastEach||(this.lastEach=0),this.indexes||(this.indexes={}),this.lastEach+=1;let e=this.lastEach;return this.indexes[e]=0,e}};f.registerParse=e=>{r=e},f.registerRule=e=>{n=e},f.registerAtRule=e=>{i=e},f.registerRoot=e=>{o=e},t.exports=f,f.default=f,f.rebuild=e=>{"atrule"===e.type?Object.setPrototypeOf(e,i.prototype):"rule"===e.type?Object.setPrototypeOf(e,n.prototype):"decl"===e.type?Object.setPrototypeOf(e,l.prototype):"comment"===e.type?Object.setPrototypeOf(e,c.prototype):"root"===e.type&&Object.setPrototypeOf(e,o.prototype),e[a]=!0,e.nodes&&e.nodes.forEach((e=>{f.rebuild(e)}))}})),rr=f(((e,t)=>{g();var r,n,i=tr(),o=class extends i{constructor(e){super({type:"document",...e}),this.nodes||(this.nodes=[])}toResult(e={}){return new r(new n,this,e).stringify()}};o.registerLazyResult=e=>{r=e},o.registerProcessor=e=>{n=e},t.exports=o,o.default=o})),nr=f(((e,t)=>{g();var r={};t.exports=function(e){r[e]||(r[e]=!0,"undefined"!=typeof console&&console.warn&&console.warn(e))}})),ir=f(((e,t)=>{g();var r=class{constructor(e,t={}){if(this.type="warning",this.text=e,t.node&&t.node.source){let e=t.node.rangeBy(t);this.line=e.start.line,this.column=e.start.column,this.endLine=e.end.line,this.endColumn=e.end.column}for(let e in t)this[e]=t[e]}toString(){return this.node?this.node.error(this.text,{plugin:this.plugin,index:this.index,word:this.word}).message:this.plugin?this.plugin+": "+this.text:this.text}};t.exports=r,r.default=r})),or=f(((e,t)=>{g();var r=ir(),n=class{constructor(e,t,r){this.processor=e,this.messages=[],this.root=t,this.opts=r,this.css=void 0,this.map=void 0}toString(){return this.css}warn(e,t={}){t.plugin||this.lastPlugin&&this.lastPlugin.postcssPlugin&&(t.plugin=this.lastPlugin.postcssPlugin);let n=new r(e,t);return this.messages.push(n),n}warnings(){return this.messages.filter((e=>"warning"===e.type))}get content(){return this.css}};t.exports=n,n.default=n})),sr=f(((e,t)=>{g();var r="'".charCodeAt(0),n='"'.charCodeAt(0),i="\\".charCodeAt(0),o="/".charCodeAt(0),s="\n".charCodeAt(0),a=" ".charCodeAt(0),l="\f".charCodeAt(0),c="\t".charCodeAt(0),u="\r".charCodeAt(0),d="[".charCodeAt(0),p="]".charCodeAt(0),f="(".charCodeAt(0),h=")".charCodeAt(0),m="{".charCodeAt(0),y="}".charCodeAt(0),b=";".charCodeAt(0),v="*".charCodeAt(0),w=":".charCodeAt(0),x="@".charCodeAt(0),k=/[\t\n\f\r "#'()/;[\\\]{}]/g,S=/[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g,C=/.[\n"'(/\\]/,A=/[\da-f]/i;t.exports=function(e,t={}){let g,O,E,_,T,P,j,I,D,$,B=e.css.valueOf(),R=t.ignoreErrors,M=B.length,U=0,z=[],L=[];function F(t){throw e.error("Unclosed "+t,U)}return{back:function(e){L.push(e)},nextToken:function(e){if(L.length)return L.pop();if(U>=M)return;let t=!!e&&e.ignoreUnclosed;switch(g=B.charCodeAt(U),g){case s:case a:case c:case u:case l:O=U;do{O+=1,g=B.charCodeAt(O)}while(g===a||g===s||g===c||g===u||g===l);$=["space",B.slice(U,O)],U=O-1;break;case d:case p:case m:case y:case w:case b:case h:{let e=String.fromCharCode(g);$=[e,e,U];break}case f:if(I=z.length?z.pop()[1]:"",D=B.charCodeAt(U+1),"url"===I&&D!==r&&D!==n&&D!==a&&D!==s&&D!==c&&D!==l&&D!==u){O=U;do{if(P=!1,O=B.indexOf(")",O+1),-1===O){if(R||t){O=U;break}F("bracket")}for(j=O;B.charCodeAt(j-1)===i;)j-=1,P=!P}while(P);$=["brackets",B.slice(U,O+1),U,O],U=O}else O=B.indexOf(")",U+1),_=B.slice(U,O+1),-1===O||C.test(_)?$=["(","(",U]:($=["brackets",_,U,O],U=O);break;case r:case n:E=g===r?"'":'"',O=U;do{if(P=!1,O=B.indexOf(E,O+1),-1===O){if(R||t){O=U+1;break}F("string")}for(j=O;B.charCodeAt(j-1)===i;)j-=1,P=!P}while(P);$=["string",B.slice(U,O+1),U,O],U=O;break;case x:k.lastIndex=U+1,k.test(B),O=0===k.lastIndex?B.length-1:k.lastIndex-2,$=["at-word",B.slice(U,O+1),U,O],U=O;break;case i:for(O=U,T=!0;B.charCodeAt(O+1)===i;)O+=1,T=!T;if(g=B.charCodeAt(O+1),T&&g!==o&&g!==a&&g!==s&&g!==c&&g!==u&&g!==l&&(O+=1,A.test(B.charAt(O)))){for(;A.test(B.charAt(O+1));)O+=1;B.charCodeAt(O+1)===a&&(O+=1)}$=["word",B.slice(U,O+1),U,O],U=O;break;default:g===o&&B.charCodeAt(U+1)===v?(O=B.indexOf("*/",U+2)+1,0===O&&(R||t?O=B.length:F("comment")),$=["comment",B.slice(U,O+1),U,O],U=O):(S.lastIndex=U+1,S.test(B),O=0===S.lastIndex?B.length-1:S.lastIndex-2,$=["word",B.slice(U,O+1),U,O],z.push($),U=O)}return U++,$},endOfFile:function(){return 0===L.length&&U>=M},position:function(){return U}}}})),ar=f(((e,t)=>{g();var r=tr(),n=class extends r{constructor(e){super(e),this.type="atrule"}append(...e){return this.proxyOf.nodes||(this.nodes=[]),super.append(...e)}prepend(...e){return this.proxyOf.nodes||(this.nodes=[]),super.prepend(...e)}};t.exports=n,n.default=n,r.registerAtRule(n)})),lr=f(((e,t)=>{g();var r,n,i=tr(),o=class extends i{constructor(e){super(e),this.type="root",this.nodes||(this.nodes=[])}removeChild(e,t){let r=this.index(e);return!t&&0===r&&this.nodes.length>1&&(this.nodes[1].raws.before=this.nodes[r].raws.before),super.removeChild(e)}normalize(e,t,r){let n=super.normalize(e);if(t)if("prepend"===r)this.nodes.length>1?t.raws.before=this.nodes[1].raws.before:delete t.raws.before;else if(this.first!==t)for(let e of n)e.raws.before=t.raws.before;return n}toResult(e={}){return new r(new n,this,e).stringify()}};o.registerLazyResult=e=>{r=e},o.registerProcessor=e=>{n=e},t.exports=o,o.default=o,i.registerRoot(o)})),cr=f(((e,t)=>{g();var r={split(e,t,r){let n=[],i="",o=!1,s=0,a=!1,l="",c=!1;for(let r of e)c?c=!1:"\\"===r?c=!0:a?r===l&&(a=!1):'"'===r||"'"===r?(a=!0,l=r):"("===r?s+=1:")"===r?s>0&&(s-=1):0===s&&t.includes(r)&&(o=!0),o?(""!==i&&n.push(i.trim()),i="",o=!1):i+=r;return(r||""!==i)&&n.push(i.trim()),n},space:e=>r.split(e,[" ","\n","\t"]),comma:e=>r.split(e,[","],!0)};t.exports=r,r.default=r})),ur=f(((e,t)=>{g();var r=tr(),n=cr(),i=class extends r{constructor(e){super(e),this.type="rule",this.nodes||(this.nodes=[])}get selectors(){return n.comma(this.selector)}set selectors(e){let t=this.selector?this.selector.match(/,\s*/):null,r=t?t[0]:","+this.raw("between","beforeOpen");this.selector=e.join(r)}};t.exports=i,i.default=i,r.registerRule(i)})),dr=f(((e,t)=>{g();var r=Kt(),n=sr(),i=er(),o=ar(),s=lr(),a=ur(),l={empty:!0,space:!0};t.exports=class{constructor(e){this.input=e,this.root=new s,this.current=this.root,this.spaces="",this.semicolon=!1,this.customProperty=!1,this.createTokenizer(),this.root.source={input:e,start:{offset:0,line:1,column:1}}}createTokenizer(){this.tokenizer=n(this.input)}parse(){let e;for(;!this.tokenizer.endOfFile();)switch(e=this.tokenizer.nextToken(),e[0]){case"space":this.spaces+=e[1];break;case";":this.freeSemicolon(e);break;case"}":this.end(e);break;case"comment":this.comment(e);break;case"at-word":this.atrule(e);break;case"{":this.emptyRule(e);break;default:this.other(e)}this.endFile()}comment(e){let t=new i;this.init(t,e[2]),t.source.end=this.getPosition(e[3]||e[2]);let r=e[1].slice(2,-2);if(/^\s*$/.test(r))t.text="",t.raws.left=r,t.raws.right="";else{let e=r.match(/^(\s*)([^]*\S)(\s*)$/);t.text=e[2],t.raws.left=e[1],t.raws.right=e[3]}}emptyRule(e){let t=new a;this.init(t,e[2]),t.selector="",t.raws.between="",this.current=t}other(e){let t=!1,r=null,n=!1,i=null,o=[],s=e[1].startsWith("--"),a=[],l=e;for(;l;){if(r=l[0],a.push(l),"("===r||"["===r)i||(i=l),o.push("("===r?")":"]");else if(s&&n&&"{"===r)i||(i=l),o.push("}");else if(0===o.length){if(";"===r){if(n)return void this.decl(a,s);break}if("{"===r)return void this.rule(a);if("}"===r){this.tokenizer.back(a.pop()),t=!0;break}":"===r&&(n=!0)}else r===o[o.length-1]&&(o.pop(),0===o.length&&(i=null));l=this.tokenizer.nextToken()}if(this.tokenizer.endOfFile()&&(t=!0),o.length>0&&this.unclosedBracket(i),t&&n){if(!s)for(;a.length&&(l=a[a.length-1][0],"space"===l||"comment"===l);)this.tokenizer.back(a.pop());this.decl(a,s)}else this.unknownWord(a)}rule(e){e.pop();let t=new a;this.init(t,e[0][2]),t.raws.between=this.spacesAndCommentsFromEnd(e),this.raw(t,"selector",e),this.current=t}decl(e,t){let n=new r;this.init(n,e[0][2]);let i,o=e[e.length-1];for(";"===o[0]&&(this.semicolon=!0,e.pop()),n.source.end=this.getPosition(o[3]||o[2]||function(e){for(let t=e.length-1;t>=0;t--){let r=e[t],n=r[3]||r[2];if(n)return n}}(e));"word"!==e[0][0];)1===e.length&&this.unknownWord(e),n.raws.before+=e.shift()[1];for(n.source.start=this.getPosition(e[0][2]),n.prop="";e.length;){let t=e[0][0];if(":"===t||"space"===t||"comment"===t)break;n.prop+=e.shift()[1]}for(n.raws.between="";e.length;){if(i=e.shift(),":"===i[0]){n.raws.between+=i[1];break}"word"===i[0]&&/\w/.test(i[1])&&this.unknownWord([i]),n.raws.between+=i[1]}("_"===n.prop[0]||"*"===n.prop[0])&&(n.raws.before+=n.prop[0],n.prop=n.prop.slice(1));let s,a=[];for(;e.length&&(s=e[0][0],"space"===s||"comment"===s);)a.push(e.shift());this.precheckMissedSemicolon(e);for(let t=e.length-1;t>=0;t--){if(i=e[t],"!important"===i[1].toLowerCase()){n.important=!0;let r=this.stringFrom(e,t);r=this.spacesFromEnd(e)+r," !important"!==r&&(n.raws.important=r);break}if("important"===i[1].toLowerCase()){let r=e.slice(0),i="";for(let e=t;e>0;e--){let t=r[e][0];if(0===i.trim().indexOf("!")&&"space"!==t)break;i=r.pop()[1]+i}0===i.trim().indexOf("!")&&(n.important=!0,n.raws.important=i,e=r)}if("space"!==i[0]&&"comment"!==i[0])break}e.some((e=>"space"!==e[0]&&"comment"!==e[0]))&&(n.raws.between+=a.map((e=>e[1])).join(""),a=[]),this.raw(n,"value",a.concat(e),t),n.value.includes(":")&&!t&&this.checkMissedSemicolon(e)}atrule(e){let t=new o;t.name=e[1].slice(1),""===t.name&&this.unnamedAtrule(t,e),this.init(t,e[2]);let r,n,i,s=!1,a=!1,l=[],c=[];for(;!this.tokenizer.endOfFile();){if(r=(e=this.tokenizer.nextToken())[0],"("===r||"["===r?c.push("("===r?")":"]"):"{"===r&&c.length>0?c.push("}"):r===c[c.length-1]&&c.pop(),0===c.length){if(";"===r){t.source.end=this.getPosition(e[2]),this.semicolon=!0;break}if("{"===r){a=!0;break}if("}"===r){if(l.length>0){for(i=l.length-1,n=l[i];n&&"space"===n[0];)n=l[--i];n&&(t.source.end=this.getPosition(n[3]||n[2]))}this.end(e);break}l.push(e)}else l.push(e);if(this.tokenizer.endOfFile()){s=!0;break}}t.raws.between=this.spacesAndCommentsFromEnd(l),l.length?(t.raws.afterName=this.spacesAndCommentsFromStart(l),this.raw(t,"params",l),s&&(e=l[l.length-1],t.source.end=this.getPosition(e[3]||e[2]),this.spaces=t.raws.between,t.raws.between="")):(t.raws.afterName="",t.params=""),a&&(t.nodes=[],this.current=t)}end(e){this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.semicolon=!1,this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.spaces="",this.current.parent?(this.current.source.end=this.getPosition(e[2]),this.current=this.current.parent):this.unexpectedClose(e)}endFile(){this.current.parent&&this.unclosedBlock(),this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.current.raws.after=(this.current.raws.after||"")+this.spaces}freeSemicolon(e){if(this.spaces+=e[1],this.current.nodes){let e=this.current.nodes[this.current.nodes.length-1];e&&"rule"===e.type&&!e.raws.ownSemicolon&&(e.raws.ownSemicolon=this.spaces,this.spaces="")}}getPosition(e){let t=this.input.fromOffset(e);return{offset:e,line:t.line,column:t.col}}init(e,t){this.current.push(e),e.source={start:this.getPosition(t),input:this.input},e.raws.before=this.spaces,this.spaces="","comment"!==e.type&&(this.semicolon=!1)}raw(e,t,r,n){let i,o,s,a,c=r.length,u="",d=!0;for(let e=0;ee+t[1]),"");e.raws[t]={value:u,raw:n}}e[t]=u}spacesAndCommentsFromEnd(e){let t,r="";for(;e.length&&(t=e[e.length-1][0],"space"===t||"comment"===t);)r=e.pop()[1]+r;return r}spacesAndCommentsFromStart(e){let t,r="";for(;e.length&&(t=e[0][0],"space"===t||"comment"===t);)r+=e.shift()[1];return r}spacesFromEnd(e){let t,r="";for(;e.length&&(t=e[e.length-1][0],"space"===t);)r=e.pop()[1]+r;return r}stringFrom(e,t){let r="";for(let n=t;n=0&&(r=e[i],"space"===r[0]||(n+=1,2!==n));i--);throw this.input.error("Missed semicolon","word"===r[0]?r[3]+1:r[2])}}})),pr=f((()=>{g()})),fr=f(((e,t)=>{g(),t.exports={nanoid:(e=21)=>{let t="",r=e;for(;r--;)t+="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"[64*Math.random()|0];return t},customAlphabet:(e,t=21)=>(r=t)=>{let n="",i=r;for(;i--;)n+=e[Math.random()*e.length|0];return n}}})),hr=f(((e,t)=>{g(),t.exports={}})),mr=f(((e,t)=>{g();var{SourceMapConsumer:r,SourceMapGenerator:n}=pr(),{fileURLToPath:i,pathToFileURL:o}=(qt(),Ft),{resolve:s,isAbsolute:a}=(Rt(),$t),{nanoid:l}=fr(),c=Gt(),u=Yt(),d=hr(),p=Symbol("fromOffsetCache"),f=Boolean(r&&n),h=Boolean(s&&a),m=class{constructor(e,t={}){if(null==e||"object"==typeof e&&!e.toString)throw new Error(`PostCSS received ${e} instead of CSS string`);if(this.css=e.toString(),"\ufeff"===this.css[0]||"￾"===this.css[0]?(this.hasBOM=!0,this.css=this.css.slice(1)):this.hasBOM=!1,t.from&&(!h||/^\w+:\/\//.test(t.from)||a(t.from)?this.file=t.from:this.file=s(t.from)),h&&f){let e=new d(this.css,t);if(e.text){this.map=e;let t=e.consumer().file;!this.file&&t&&(this.file=this.mapResolve(t))}}this.file||(this.id=""),this.map&&(this.map.file=this.from)}fromOffset(e){let t,r;if(this[p])r=this[p];else{let e=this.css.split("\n");r=new Array(e.length);let t=0;for(let n=0,i=e.length;n=t)n=r.length-1;else{let t,i=r.length-2;for(;n>1),e=r[t+1])){n=t;break}n=t+1}}return{line:n+1,col:e-r[n]+1}}error(e,t,r,n={}){let i,s,a;if(t&&"object"==typeof t){let e=t,n=r;if("number"==typeof e.offset){let n=this.fromOffset(e.offset);t=n.line,r=n.col}else t=e.line,r=e.column;if("number"==typeof n.offset){let e=this.fromOffset(n.offset);s=e.line,a=e.col}else s=n.line,a=n.column}else if(!r){let e=this.fromOffset(t);t=e.line,r=e.col}let l=this.origin(t,r,s,a);return i=l?new u(e,void 0===l.endLine?l.line:{line:l.line,column:l.column},void 0===l.endLine?l.column:{line:l.endLine,column:l.endColumn},l.source,l.file,n.plugin):new u(e,void 0===s?t:{line:t,column:r},void 0===s?r:{line:s,column:a},this.css,this.file,n.plugin),i.input={line:t,column:r,endLine:s,endColumn:a,source:this.css},this.file&&(o&&(i.input.url=o(this.file).toString()),i.input.file=this.file),i}origin(e,t,r,n){if(!this.map)return!1;let s,l,c=this.map.consumer(),u=c.originalPositionFor({line:e,column:t});if(!u.source)return!1;"number"==typeof r&&(s=c.originalPositionFor({line:r,column:n})),l=a(u.source)?o(u.source):new URL(u.source,this.map.consumer().sourceRoot||o(this.map.mapFile));let d={url:l.toString(),line:u.line,column:u.column,endLine:s&&s.line,endColumn:s&&s.column};if("file:"===l.protocol){if(!i)throw new Error("file: protocol is not available in this PostCSS build");d.file=i(l)}let p=c.sourceContentFor(u.source);return p&&(d.source=p),d}mapResolve(e){return/^\w+:\/\//.test(e)?e:s(this.map.consumer().sourceRoot||this.map.root||".",e)}get from(){return this.file||this.id}toJSON(){let e={};for(let t of["hasBOM","css","file","id"])null!=this[t]&&(e[t]=this[t]);return this.map&&(e.map={...this.map},e.map.consumerCache&&(e.map.consumerCache=void 0)),e}};t.exports=m,m.default=m,c&&c.registerInput&&c.registerInput(m)})),gr=f(((e,t)=>{g();var r=tr(),n=dr(),i=mr();function o(e,t){let r=new i(e,t),o=new n(r);try{o.parse()}catch(e){throw e}return o.root}t.exports=o,o.default=o,r.registerParse(o)})),yr=f(((e,t)=>{g();var{isClean:r,my:n}=Ht(),i=Zt(),o=Jt(),s=tr(),a=rr(),l=(nr(),or()),c=gr(),u=lr(),d={document:"Document",root:"Root",atrule:"AtRule",rule:"Rule",decl:"Declaration",comment:"Comment"},p={postcssPlugin:!0,prepare:!0,Once:!0,Document:!0,Root:!0,Declaration:!0,Rule:!0,AtRule:!0,Comment:!0,DeclarationExit:!0,RuleExit:!0,AtRuleExit:!0,CommentExit:!0,RootExit:!0,DocumentExit:!0,OnceExit:!0},f={postcssPlugin:!0,prepare:!0,Once:!0},h=0;function m(e){return"object"==typeof e&&"function"==typeof e.then}function y(e){let t=!1,r=d[e.type];return"decl"===e.type?t=e.prop.toLowerCase():"atrule"===e.type&&(t=e.name.toLowerCase()),t&&e.append?[r,r+"-"+t,h,r+"Exit",r+"Exit-"+t]:t?[r,r+"-"+t,r+"Exit",r+"Exit-"+t]:e.append?[r,h,r+"Exit"]:[r,r+"Exit"]}function b(e){let t;return t="document"===e.type?["Document",h,"DocumentExit"]:"root"===e.type?["Root",h,"RootExit"]:y(e),{node:e,events:t,eventIndex:0,visitors:[],visitorIndex:0,iterator:0}}function v(e){return e[r]=!1,e.nodes&&e.nodes.forEach((e=>v(e))),e}var w={},x=class{constructor(e,t,r){let i;if(this.stringified=!1,this.processed=!1,"object"!=typeof t||null===t||"root"!==t.type&&"document"!==t.type)if(t instanceof x||t instanceof l)i=v(t.root),t.map&&(void 0===r.map&&(r.map={}),r.map.inline||(r.map.inline=!1),r.map.prev=t.map);else{let e=c;r.syntax&&(e=r.syntax.parse),r.parser&&(e=r.parser),e.parse&&(e=e.parse);try{i=e(t,r)}catch(e){this.processed=!0,this.error=e}i&&!i[n]&&s.rebuild(i)}else i=v(t);this.result=new l(e,i,r),this.helpers={...w,result:this.result,postcss:w},this.plugins=this.processor.plugins.map((e=>"object"==typeof e&&e.prepare?{...e,...e.prepare(this.result)}:e))}get[Symbol.toStringTag](){return"LazyResult"}get processor(){return this.result.processor}get opts(){return this.result.opts}get css(){return this.stringify().css}get content(){return this.stringify().content}get map(){return this.stringify().map}get root(){return this.sync().root}get messages(){return this.sync().messages}warnings(){return this.sync().warnings()}toString(){return this.css}then(e,t){return this.async().then(e,t)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}async(){return this.error?Promise.reject(this.error):this.processed?Promise.resolve(this.result):(this.processing||(this.processing=this.runAsync()),this.processing)}sync(){if(this.error)throw this.error;if(this.processed)return this.result;if(this.processed=!0,this.processing)throw this.getAsyncError();for(let e of this.plugins)if(m(this.runOnRoot(e)))throw this.getAsyncError();if(this.prepareVisitors(),this.hasListener){let e=this.result.root;for(;!e[r];)e[r]=!0,this.walkSync(e);if(this.listeners.OnceExit)if("document"===e.type)for(let t of e.nodes)this.visitSync(this.listeners.OnceExit,t);else this.visitSync(this.listeners.OnceExit,e)}return this.result}stringify(){if(this.error)throw this.error;if(this.stringified)return this.result;this.stringified=!0,this.sync();let e=this.result.opts,t=o;e.syntax&&(t=e.syntax.stringify),e.stringifier&&(t=e.stringifier),t.stringify&&(t=t.stringify);let r=new i(t,this.result.root,this.result.opts).generate();return this.result.css=r[0],this.result.map=r[1],this.result}walkSync(e){e[r]=!0;let t=y(e);for(let n of t)if(n===h)e.nodes&&e.each((e=>{e[r]||this.walkSync(e)}));else{let t=this.listeners[n];if(t&&this.visitSync(t,e.toProxy()))return}}visitSync(e,t){for(let[r,n]of e){let e;this.result.lastPlugin=r;try{e=n(t,this.helpers)}catch(e){throw this.handleError(e,t.proxyOf)}if("root"!==t.type&&"document"!==t.type&&!t.parent)return!0;if(m(e))throw this.getAsyncError()}}runOnRoot(e){this.result.lastPlugin=e;try{if("object"==typeof e&&e.Once){if("document"===this.result.root.type){let t=this.result.root.nodes.map((t=>e.Once(t,this.helpers)));return m(t[0])?Promise.all(t):t}return e.Once(this.result.root,this.helpers)}if("function"==typeof e)return e(this.result.root,this.result)}catch(e){throw this.handleError(e)}}getAsyncError(){throw new Error("Use process(css).then(cb) to work with async plugins")}handleError(e,t){let r=this.result.lastPlugin;try{t&&t.addToError(e),this.error=e,"CssSyntaxError"!==e.name||e.plugin?r.postcssVersion:(e.plugin=r.postcssPlugin,e.setMessage())}catch(e){console&&console.error&&console.error(e)}return e}async runAsync(){this.plugin=0;for(let e=0;e0;){let e=this.visitTick(t);if(m(e))try{await e}catch(e){let r=t[t.length-1].node;throw this.handleError(e,r)}}}if(this.listeners.OnceExit)for(let[t,r]of this.listeners.OnceExit){this.result.lastPlugin=t;try{if("document"===e.type){let t=e.nodes.map((e=>r(e,this.helpers)));await Promise.all(t)}else await r(e,this.helpers)}catch(e){throw this.handleError(e)}}}return this.processed=!0,this.stringify()}prepareVisitors(){this.listeners={};let e=(e,t,r)=>{this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push([e,r])};for(let t of this.plugins)if("object"==typeof t)for(let r in t){if(!p[r]&&/^[A-Z]/.test(r))throw new Error(`Unknown event ${r} in ${t.postcssPlugin}. Try to update PostCSS (${this.processor.version} now).`);if(!f[r])if("object"==typeof t[r])for(let n in t[r])e(t,"*"===n?r:r+"-"+n.toLowerCase(),t[r][n]);else"function"==typeof t[r]&&e(t,r,t[r])}this.hasListener=Object.keys(this.listeners).length>0}visitTick(e){let t=e[e.length-1],{node:n,visitors:i}=t;if("root"!==n.type&&"document"!==n.type&&!n.parent)return void e.pop();if(i.length>0&&t.visitorIndex{w=e},t.exports=x,x.default=x,u.registerLazyResult(x),a.registerLazyResult(x)})),br=f(((e,t)=>{g();var r=Zt(),n=Jt(),i=(nr(),gr()),o=or(),s=class{constructor(e,t,i){t=t.toString(),this.stringified=!1,this._processor=e,this._css=t,this._opts=i,this._map=void 0;let s,a=n;this.result=new o(this._processor,s,this._opts),this.result.css=t;let l=this;Object.defineProperty(this.result,"root",{get:()=>l.root});let c=new r(a,s,this._opts,t);if(c.isMap()){let[e,t]=c.generate();e&&(this.result.css=e),t&&(this.result.map=t)}}get[Symbol.toStringTag](){return"NoWorkResult"}get processor(){return this.result.processor}get opts(){return this.result.opts}get css(){return this.result.css}get content(){return this.result.css}get map(){return this.result.map}get root(){if(this._root)return this._root;let e,t=i;try{e=t(this._css,this._opts)}catch(e){this.error=e}if(this.error)throw this.error;return this._root=e,e}get messages(){return[]}warnings(){return[]}toString(){return this._css}then(e,t){return this.async().then(e,t)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}async(){return this.error?Promise.reject(this.error):Promise.resolve(this.result)}sync(){if(this.error)throw this.error;return this.result}};t.exports=s,s.default=s})),vr=f(((e,t)=>{g();var r=br(),n=yr(),i=rr(),o=lr(),s=class{constructor(e=[]){this.version="8.4.24",this.plugins=this.normalize(e)}use(e){return this.plugins=this.plugins.concat(this.normalize([e])),this}process(e,t={}){return 0===this.plugins.length&&void 0===t.parser&&void 0===t.stringifier&&void 0===t.syntax?new r(this,e,t):new n(this,e,t)}normalize(e){let t=[];for(let r of e)if(!0===r.postcss?r=r():r.postcss&&(r=r.postcss),"object"==typeof r&&Array.isArray(r.plugins))t=t.concat(r.plugins);else if("object"==typeof r&&r.postcssPlugin)t.push(r);else if("function"==typeof r)t.push(r);else if("object"!=typeof r||!r.parse&&!r.stringify)throw new Error(r+" is not a PostCSS plugin");return t}};t.exports=s,s.default=s,o.registerProcessor(s),i.registerProcessor(s)})),wr=f(((e,t)=>{g();var r=Kt(),n=hr(),i=er(),o=ar(),s=mr(),a=lr(),l=ur();function c(e,t){if(Array.isArray(e))return e.map((e=>c(e)));let{inputs:u,...d}=e;if(u){t=[];for(let e of u){let r={...e,__proto__:s.prototype};r.map&&(r.map={...r.map,__proto__:n.prototype}),t.push(r)}}if(d.nodes&&(d.nodes=e.nodes.map((e=>c(e,t)))),d.source){let{inputId:e,...r}=d.source;d.source=r,null!=e&&(d.source.input=t[e])}if("root"===d.type)return new a(d);if("decl"===d.type)return new r(d);if("rule"===d.type)return new l(d);if("comment"===d.type)return new i(d);if("atrule"===d.type)return new o(d);throw new Error("Unknown node type: "+e.type)}t.exports=c,c.default=c})),xr=f(((t,r)=>{g();var n=Yt(),i=Kt(),o=yr(),s=tr(),a=vr(),l=Jt(),c=wr(),u=rr(),d=ir(),p=er(),f=ar(),h=or(),m=mr(),y=gr(),b=cr(),v=ur(),w=lr(),x=Xt();function k(...e){return 1===e.length&&Array.isArray(e[0])&&(e=e[0]),new a(e)}k.plugin=function(t,r){let n,i=!1;function o(...n){console&&console.warn&&!i&&(i=!0,console.warn(t+": postcss.plugin was deprecated. Migration guide:\nhttps://evilmartians.com/chronicles/postcss-8-plugin-migration"),e.env.LANG&&e.env.LANG.startsWith("cn")&&console.warn(t+": 里面 postcss.plugin 被弃用. 迁移指南:\nhttps://www.w3ctech.com/topic/2226"));let o=r(...n);return o.postcssPlugin=t,o.postcssVersion=(new a).version,o}return Object.defineProperty(o,"postcss",{get:()=>(n||(n=o()),n)}),o.process=function(e,t,r){return k([o(r)]).process(e,t)},o},k.stringify=l,k.parse=y,k.fromJSON=c,k.list=b,k.comment=e=>new p(e),k.atRule=e=>new f(e),k.decl=e=>new i(e),k.rule=e=>new v(e),k.root=e=>new w(e),k.document=e=>new u(e),k.CssSyntaxError=n,k.Declaration=i,k.Container=s,k.Processor=a,k.Document=u,k.Comment=p,k.Warning=d,k.AtRule=f,k.Result=h,k.Input=m,k.Rule=v,k.Root=w,k.Node=x,o.registerPostcss(k),r.exports=k,k.default=k})),kr=p((()=>{g(),Vt=m(xr()),Wt=Vt.default,Vt.default.stringify,Vt.default.fromJSON,Vt.default.plugin,Vt.default.parse,Vt.default.list,Vt.default.document,Vt.default.comment,Vt.default.atRule,Vt.default.rule,Vt.default.decl,Vt.default.root,Vt.default.CssSyntaxError,Vt.default.Declaration,Vt.default.Container,Vt.default.Processor,Vt.default.Document,Vt.default.Comment,Vt.default.Warning,Vt.default.AtRule,Vt.default.Result,Vt.default.Input,Vt.default.Rule,Vt.default.Root,Vt.default.Node})),Sr=f(((e,t)=>{g(),t.exports=function(e,t,r,n,i){for(t=t.split?t.split("."):t,n=0;n{function r(e){for(var t=e.toLowerCase(),r="",n=!1,i=0;i<6&&void 0!==t[i];i++){var o=t.charCodeAt(i);if(n=32===o,!(o>=97&&o<=102||o>=48&&o<=57))break;r+=t[i]}if(0!==r.length){var s=parseInt(r,16);return s>=55296&&s<=57343||0===s||s>1114111?["�",r.length+(n?1:0)]:[String.fromCodePoint(s),r.length+(n?1:0)]}}g(),e.__esModule=!0,e.default=function(e){if(!n.test(e))return e;for(var t="",i=0;i{g(),e.__esModule=!0,e.default=function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),n=1;n0;){var i=r.shift();if(!e[i])return;e=e[i]}return e},t.exports=e.default})),Or=f(((e,t)=>{g(),e.__esModule=!0,e.default=function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),n=1;n0;){var i=r.shift();e[i]||(e[i]={}),e=e[i]}},t.exports=e.default})),Er=f(((e,t)=>{g(),e.__esModule=!0,e.default=function(e){for(var t="",r=e.indexOf("/*"),n=0;r>=0;){t+=e.slice(n,r);var i=e.indexOf("*/",r+2);if(i<0)return t;n=i+2,r=e.indexOf("/*",n)}return t+e.slice(n)},t.exports=e.default})),_r=f((e=>{g(),e.__esModule=!0,e.unesc=e.stripComments=e.getProp=e.ensureObject=void 0;var t=o(Cr());e.unesc=t.default;var r=o(Ar());e.getProp=r.default;var n=o(Or());e.ensureObject=n.default;var i=o(Er());function o(e){return e&&e.__esModule?e:{default:e}}e.stripComments=i.default})),Tr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r=_r();function n(e,t){for(var r=0;re||this.source.end.linet||this.source.end.line===e&&this.source.end.column{g(),e.__esModule=!0,e.UNIVERSAL=e.TAG=e.STRING=e.SELECTOR=e.ROOT=e.PSEUDO=e.NESTING=e.ID=e.COMMENT=e.COMBINATOR=e.CLASS=e.ATTRIBUTE=void 0,e.TAG="tag",e.STRING="string",e.SELECTOR="selector",e.ROOT="root",e.PSEUDO="pseudo",e.NESTING="nesting",e.ID="id",e.COMMENT="comment",e.COMBINATOR="combinator",e.CLASS="class",e.ATTRIBUTE="attribute",e.UNIVERSAL="universal"})),jr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Tr())&&r.__esModule?r:{default:r},i=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=o(t);if(r&&r.has(e))return r.get(e);var n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=i?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(n,s,a):n[s]=e[s]}return n.default=e,r&&r.set(e,n),n}(Pr());function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(o=function(e){return e?r:t})(e)}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r=e&&(this.indexes[r]=t-1);return this},r.removeAll=function(){for(var e,t=function(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(r)return(r=r.call(e)).next.bind(r);if(Array.isArray(e)||(r=function(e,t){if(e){if("string"==typeof e)return s(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);if("Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return s(e,t)}}(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0;return function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(this.nodes);!(e=t()).done;)e.value.parent=void 0;return this.nodes=[],this},r.empty=function(){return this.removeAll()},r.insertAfter=function(e,t){t.parent=this;var r,n=this.index(e);for(var i in this.nodes.splice(n+1,0,t),t.parent=this,this.indexes)n<=(r=this.indexes[i])&&(this.indexes[i]=r+1);return this},r.insertBefore=function(e,t){t.parent=this;var r,n=this.index(e);for(var i in this.nodes.splice(n,0,t),t.parent=this,this.indexes)(r=this.indexes[i])<=n&&(this.indexes[i]=r+1);return this},r._findChildAtPosition=function(e,t){var r=void 0;return this.each((function(n){if(n.atPosition){var i=n.atPosition(e,t);if(i)return r=i,!1}else if(n.isAtPosition(e,t))return r=n,!1})),r},r.atPosition=function(e,t){if(this.isAtPosition(e,t))return this._findChildAtPosition(e,t)||this},r._inferEndPosition=function(){this.last&&this.last.source&&this.last.source.end&&(this.source=this.source||{},this.source.end=this.source.end||{},Object.assign(this.source.end,this.last.source.end))},r.each=function(e){this.lastEach||(this.lastEach=0),this.indexes||(this.indexes={}),this.lastEach++;var t=this.lastEach;if(this.indexes[t]=0,this.length){for(var r,n;this.indexes[t]{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=jr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){for(var r=0;r{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=jr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.SELECTOR,r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t}(n.default);e.default=s,t.exports=e.default})),$r=f(((e,t)=>{g();var r={}.hasOwnProperty,n=/[ -,\.\/:-@\[-\^`\{-~]/,i=/[ -,\.\/:-@\[\]\^`\{-~]/,o=/(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g,s=function e(t,s){s=function(e,t){if(!e)return t;var n={};for(var i in t)n[i]=r.call(e,i)?e[i]:t[i];return n}(s,e.options),"single"!=s.quotes&&"double"!=s.quotes&&(s.quotes="single");for(var a="double"==s.quotes?'"':"'",l=s.isIdentifier,c=t.charAt(0),u="",d=0,p=t.length;d126){if(h>=55296&&h<=56319&&d{g(),e.__esModule=!0,e.default=void 0;var r=s($r()),n=_r(),i=s(Tr()),o=Pr();function s(e){return e&&e.__esModule?e:{default:e}}function a(e,t){for(var r=0;r{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Tr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.COMMENT,r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t}(n.default);e.default=s,t.exports=e.default})),Mr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Tr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.ID,r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t.prototype.valueToString=function(){return"#"+e.prototype.valueToString.call(this)},t}(n.default);e.default=s,t.exports=e.default})),Ur=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r=i($r()),n=_r();function i(e){return e&&e.__esModule?e:{default:e}}function o(e,t){for(var r=0;r{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Ur())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.TAG,r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t}(n.default);e.default=s,t.exports=e.default})),Lr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Tr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.STRING,r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t}(n.default);e.default=s,t.exports=e.default})),Fr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=jr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.PSEUDO,r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t.prototype.toString=function(){var e=this.length?"("+this.map(String).join(",")+")":"";return[this.rawSpaceBefore,this.stringifyProperty("value"),e,this.rawSpaceAfter].join("")},t}(n.default);e.default=s,t.exports=e.default})),Nr={};function Vr(e){return e}h(Nr,{deprecate:()=>Vr});var Wr=p((()=>{g()})),qr=f(((e,t)=>{g(),t.exports=(Wr(),Nr).deprecate})),Gr=f((e=>{g(),e.__esModule=!0,e.default=void 0,e.unescapeValue=h;var t,r=s($r()),n=s(Cr()),i=s(Ur()),o=Pr();function s(e){return e&&e.__esModule?e:{default:e}}function a(e,t){for(var r=0;r0&&!e.quoted&&0===r.before.length&&!(e.spaces.value&&e.spaces.value.after)&&(r.before=" "),b(t,r)})))),t.push("]"),t.push(this.rawSpaceAfter),t.join("")},function(e,t,r){t&&a(e.prototype,t),Object.defineProperty(e,"prototype",{writable:!1})}(t,[{key:"quoted",get:function(){var e=this.quoteMark;return"'"===e||'"'===e},set:function(e){p()}},{key:"quoteMark",get:function(){return this._quoteMark},set:function(e){this._constructed?this._quoteMark!==e&&(this._quoteMark=e,this._syncRawValue()):this._quoteMark=e}},{key:"qualifiedAttribute",get:function(){return this.qualifiedName(this.raws.attribute||this.attribute)}},{key:"insensitiveFlag",get:function(){return this.insensitive?"i":""}},{key:"value",get:function(){return this._value},set:function(e){if(this._constructed){var t=h(e),r=t.deprecatedUsage,n=t.unescaped,i=t.quoteMark;if(r&&d(),n===this._value&&i===this._quoteMark)return;this._value=n,this._quoteMark=i,this._syncRawValue()}else this._value=e}},{key:"insensitive",get:function(){return this._insensitive},set:function(e){e||(this._insensitive=!1,this.raws&&("I"===this.raws.insensitiveFlag||"i"===this.raws.insensitiveFlag)&&(this.raws.insensitiveFlag=void 0)),this._insensitive=e}},{key:"attribute",get:function(){return this._attribute},set:function(e){this._handleEscapes("attribute",e),this._attribute=e}}]),t}(i.default);e.default=m,m.NO_QUOTE=null,m.SINGLE_QUOTE="'",m.DOUBLE_QUOTE='"';var y=((t={"'":{quotes:"single",wrap:!0},'"':{quotes:"double",wrap:!0}}).null={isIdentifier:!0},t);function b(e,t){return""+t.before+e+t.after}})),Yr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Ur())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.UNIVERSAL,r.value="*",r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t}(n.default);e.default=s,t.exports=e.default})),Hr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Tr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.COMBINATOR,r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t}(n.default);e.default=s,t.exports=e.default})),Qr=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Tr())&&r.__esModule?r:{default:r},i=Pr();function o(e,t){return(o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var s=function(e){function t(t){var r;return(r=e.call(this,t)||this).type=i.NESTING,r.value="&",r}return function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,o(e,t)}(t,e),t}(n.default);e.default=s,t.exports=e.default})),Jr=f(((e,t)=>{g(),e.__esModule=!0,e.default=function(e){return e.sort((function(e,t){return e-t}))},t.exports=e.default})),Xr=f((e=>{g(),e.__esModule=!0,e.word=e.tilde=e.tab=e.str=e.space=e.slash=e.singleQuote=e.semicolon=e.plus=e.pipe=e.openSquare=e.openParenthesis=e.newline=e.greaterThan=e.feed=e.equals=e.doubleQuote=e.dollar=e.cr=e.comment=e.comma=e.combinator=e.colon=e.closeSquare=e.closeParenthesis=e.caret=e.bang=e.backslash=e.at=e.asterisk=e.ampersand=void 0,e.ampersand=38,e.asterisk=42,e.at=64,e.comma=44,e.colon=58,e.semicolon=59,e.openParenthesis=40,e.closeParenthesis=41,e.openSquare=91,e.closeSquare=93,e.dollar=36,e.tilde=126,e.caret=94,e.plus=43,e.equals=61,e.pipe=124,e.greaterThan=62,e.space=32,e.singleQuote=39,e.doubleQuote=34,e.slash=47,e.bang=33,e.backslash=92,e.cr=13,e.feed=12,e.newline=10,e.tab=9,e.str=39,e.comment=-1,e.word=-2,e.combinator=-3})),Kr=f((e=>{g(),e.__esModule=!0,e.FIELDS=void 0,e.default=function(e){var t,r,i,o,s,a,l,u,d,p,f,h,m=[],g=e.css.valueOf(),y=g.length,b=-1,v=1,w=0,x=0;function k(t,r){if(!e.safe)throw e.error("Unclosed "+t,v,w-b,w);u=(g+=r).length-1}for(;w0?(d=v+a,p=u-l[a].length):(d=v,p=b),h=n.comment,v=d,i=d,r=u-p):t===n.slash?(h=t,i=v,r=w-b,x=(u=w)+1):(u=c(g,w),h=n.word,i=v,r=u-b),x=u+1}m.push([h,v,w-b,i,r,w,x]),p&&(b=p,p=null),w=x}return m};var t,r,n=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=i(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=o?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(n,s,a):n[s]=e[s]}return n.default=e,r&&r.set(e,n),n}(Xr());function i(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(i=function(e){return e?r:t})(e)}var o,s=((t={})[n.tab]=!0,t[n.newline]=!0,t[n.cr]=!0,t[n.feed]=!0,t),a=((r={})[n.space]=!0,r[n.tab]=!0,r[n.newline]=!0,r[n.cr]=!0,r[n.feed]=!0,r[n.ampersand]=!0,r[n.asterisk]=!0,r[n.bang]=!0,r[n.comma]=!0,r[n.colon]=!0,r[n.semicolon]=!0,r[n.openParenthesis]=!0,r[n.closeParenthesis]=!0,r[n.openSquare]=!0,r[n.closeSquare]=!0,r[n.singleQuote]=!0,r[n.doubleQuote]=!0,r[n.plus]=!0,r[n.pipe]=!0,r[n.tilde]=!0,r[n.greaterThan]=!0,r[n.equals]=!0,r[n.dollar]=!0,r[n.caret]=!0,r[n.slash]=!0,r),l={};for(o=0;o<22;o++)l["0123456789abcdefABCDEF".charCodeAt(o)]=!0;function c(e,t){var r,i=t;do{if(r=e.charCodeAt(i),a[r])return i-1;r===n.backslash?i=u(e,i)+1:i++}while(i{g(),e.__esModule=!0,e.default=void 0;var r,n,i=C(Ir()),o=C(Dr()),s=C(Br()),a=C(Rr()),l=C(Mr()),c=C(zr()),u=C(Lr()),d=C(Fr()),p=S(Gr()),f=C(Yr()),h=C(Hr()),m=C(Qr()),y=C(Jr()),b=S(Kr()),v=S(Xr()),w=S(Pr()),x=_r();function k(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(k=function(e){return e?r:t})(e)}function S(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=k(t);if(r&&r.has(e))return r.get(e);var n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var s=i?Object.getOwnPropertyDescriptor(e,o):null;s&&(s.get||s.set)?Object.defineProperty(n,o,s):n[o]=e[o]}return n.default=e,r&&r.set(e,n),n}function C(e){return e&&e.__esModule?e:{default:e}}function A(e,t){for(var r=0;rthis.position&&(i=this.parseWhitespaceEquivalentTokens(t)),this.isNamedCombinator()?r=this.namedCombinator():this.currToken[b.FIELDS.TYPE]===v.combinator?(r=new h.default({value:this.content(),source:j(this.currToken),sourceIndex:this.currToken[b.FIELDS.START_POS]}),this.position++):O[this.currToken[b.FIELDS.TYPE]]||i||this.unexpected(),r){if(i){var o=this.convertWhitespaceNodesToSpace(i),s=o.space,a=o.rawSpace;r.spaces.before=s,r.rawSpaceBefore=a}}else{var l=this.convertWhitespaceNodesToSpace(i,!0),c=l.space,u=l.rawSpace;u||(u=c);var d={},p={spaces:{}};c.endsWith(" ")&&u.endsWith(" ")?(d.before=c.slice(0,c.length-1),p.spaces.before=u.slice(0,u.length-1)):c.startsWith(" ")&&u.startsWith(" ")?(d.after=c.slice(1),p.spaces.after=u.slice(1)):p.value=u,r=new h.default({value:" ",source:I(n,this.tokens[this.position-1]),sourceIndex:n[b.FIELDS.START_POS],spaces:d,raws:p})}return this.currToken&&this.currToken[b.FIELDS.TYPE]===v.space&&(r.spaces.after=this.optionalSpace(this.content()),this.position++),this.newNode(r)}var f=this.parseWhitespaceEquivalentTokens(t);if(f.length>0){var m=this.current.last;if(m){var g=this.convertWhitespaceNodesToSpace(f),y=g.space,w=g.rawSpace;void 0!==w&&(m.rawSpaceAfter+=w),m.spaces.after+=y}else f.forEach((function(t){return e.newNode(t)}))}},t.comma=function(){if(this.position===this.tokens.length-1)return this.root.trailingComma=!0,void this.position++;this.current._inferEndPosition();var e=new o.default({source:{start:_(this.tokens[this.position+1])}});this.current.parent.append(e),this.current=e,this.position++},t.comment=function(){var e=this.currToken;this.newNode(new a.default({value:this.content(),source:j(e),sourceIndex:e[b.FIELDS.START_POS]})),this.position++},t.error=function(e,t){throw this.root.error(e,t)},t.missingBackslash=function(){return this.error("Expected a backslash preceding the semicolon.",{index:this.currToken[b.FIELDS.START_POS]})},t.missingParenthesis=function(){return this.expected("opening parenthesis",this.currToken[b.FIELDS.START_POS])},t.missingSquareBracket=function(){return this.expected("opening square bracket",this.currToken[b.FIELDS.START_POS])},t.unexpected=function(){return this.error("Unexpected '"+this.content()+"'. Escaping special characters with \\ may help.",this.currToken[b.FIELDS.START_POS])},t.unexpectedPipe=function(){return this.error("Unexpected '|'.",this.currToken[b.FIELDS.START_POS])},t.namespace=function(){var e=this.prevToken&&this.content(this.prevToken)||!0;return this.nextToken[b.FIELDS.TYPE]===v.word?(this.position++,this.word(e)):this.nextToken[b.FIELDS.TYPE]===v.asterisk?(this.position++,this.universal(e)):void this.unexpectedPipe()},t.nesting=function(){if(this.nextToken&&"|"===this.content(this.nextToken))this.position++;else{var e=this.currToken;this.newNode(new m.default({value:this.content(),source:j(e),sourceIndex:e[b.FIELDS.START_POS]})),this.position++}},t.parentheses=function(){var e=this.current.last,t=1;if(this.position++,e&&e.type===w.PSEUDO){var r=new o.default({source:{start:_(this.tokens[this.position-1])}}),n=this.current;for(e.append(r),this.current=r;this.position1&&e.nextToken&&e.nextToken[b.FIELDS.TYPE]===v.openParenthesis&&e.error("Misplaced parenthesis.",{index:e.nextToken[b.FIELDS.START_POS]})})):this.expected(["pseudo-class","pseudo-element"],this.position-1)},t.space=function(){var e=this.content();0===this.position||this.prevToken[b.FIELDS.TYPE]===v.comma||this.prevToken[b.FIELDS.TYPE]===v.openParenthesis||this.current.nodes.every((function(e){return"comment"===e.type}))?(this.spaces=this.optionalSpace(e),this.position++):this.position===this.tokens.length-1||this.nextToken[b.FIELDS.TYPE]===v.comma||this.nextToken[b.FIELDS.TYPE]===v.closeParenthesis?(this.current.last.spaces.after=this.optionalSpace(e),this.position++):this.combinator()},t.string=function(){var e=this.currToken;this.newNode(new u.default({value:this.content(),source:j(e),sourceIndex:e[b.FIELDS.START_POS]})),this.position++},t.universal=function(e){var t=this.nextToken;if(t&&"|"===this.content(t))return this.position++,this.namespace();var r=this.currToken;this.newNode(new f.default({value:this.content(),source:j(r),sourceIndex:r[b.FIELDS.START_POS]}),e),this.position++},t.splitWord=function(e,t){for(var r=this,n=this.nextToken,i=this.content();n&&~[v.dollar,v.caret,v.equals,v.word].indexOf(n[b.FIELDS.TYPE]);){this.position++;var o=this.content();if(i+=o,o.lastIndexOf("\\")===o.length-1){var a=this.nextToken;a&&a[b.FIELDS.TYPE]===v.space&&(i+=this.requiredSpace(this.content(a)),this.position++)}n=this.nextToken}var u=$(i,".").filter((function(e){var t="\\"===i[e-1],r=/^\d+\.\d+%$/.test(i);return!t&&!r})),d=$(i,"#").filter((function(e){return"\\"!==i[e-1]})),p=$(i,"#{");p.length&&(d=d.filter((function(e){return!~p.indexOf(e)})));var f=(0,y.default)(function(){var e=Array.prototype.concat.apply([],arguments);return e.filter((function(t,r){return r===e.indexOf(t)}))}([0].concat(u,d)));f.forEach((function(n,o){var a=f[o+1]||i.length,p=i.slice(n,a);if(0===o&&t)return t.call(r,p,f.length);var h,m=r.currToken,g=m[b.FIELDS.START_POS]+f[o],y=P(m[1],m[2]+n,m[3],m[2]+(a-1));if(~u.indexOf(n)){var v={value:p.slice(1),source:y,sourceIndex:g};h=new s.default(D(v,"value"))}else if(~d.indexOf(n)){var w={value:p.slice(1),source:y,sourceIndex:g};h=new l.default(D(w,"value"))}else{var x={value:p,source:y,sourceIndex:g};D(x,"value"),h=new c.default(x)}r.newNode(h,e),e=null})),this.position++},t.word=function(e){var t=this.nextToken;return t&&"|"===this.content(t)?(this.position++,this.namespace()):this.splitWord(e)},t.loop=function(){for(;this.position{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=Zr())&&r.__esModule?r:{default:r},i=function(){function e(e,t){this.func=e||function(){},this.funcRes=null,this.options=t}var t=e.prototype;return t._shouldUpdateSelector=function(e,t){return void 0===t&&(t={}),!1!==Object.assign({},this.options,t).updateSelector&&"string"!=typeof e},t._isLossy=function(e){return void 0===e&&(e={}),!1===Object.assign({},this.options,e).lossless},t._root=function(e,t){return void 0===t&&(t={}),new n.default(e,this._parseOptions(t)).root},t._parseOptions=function(e){return{lossy:this._isLossy(e)}},t._run=function(e,t){var r=this;return void 0===t&&(t={}),new Promise((function(n,i){try{var o=r._root(e,t);Promise.resolve(r.func(o)).then((function(n){var i=void 0;return r._shouldUpdateSelector(e,t)&&(i=o.toString(),e.selector=i),{transform:n,root:o,string:i}})).then(n,i)}catch(e){return void i(e)}}))},t._runSync=function(e,t){void 0===t&&(t={});var r=this._root(e,t),n=this.func(r);if(n&&"function"==typeof n.then)throw new Error("Selector processor returned a promise to a synchronous call.");var i=void 0;return t.updateSelector&&"string"!=typeof e&&(i=r.toString(),e.selector=i),{transform:n,root:r,string:i}},t.ast=function(e,t){return this._run(e,t).then((function(e){return e.root}))},t.astSync=function(e,t){return this._runSync(e,t).root},t.transform=function(e,t){return this._run(e,t).then((function(e){return e.transform}))},t.transformSync=function(e,t){return this._runSync(e,t).transform},t.process=function(e,t){return this._run(e,t).then((function(e){return e.string||e.root.toString()}))},t.processSync=function(e,t){var r=this._runSync(e,t);return r.string||r.root.toString()},e}();e.default=i,t.exports=e.default})),tn=f((e=>{g(),e.__esModule=!0,e.universal=e.tag=e.string=e.selector=e.root=e.pseudo=e.nesting=e.id=e.comment=e.combinator=e.className=e.attribute=void 0;var t=f(Gr()),r=f(Br()),n=f(Hr()),i=f(Rr()),o=f(Mr()),s=f(Qr()),a=f(Fr()),l=f(Ir()),c=f(Dr()),u=f(Lr()),d=f(zr()),p=f(Yr());function f(e){return e&&e.__esModule?e:{default:e}}e.attribute=function(e){return new t.default(e)},e.className=function(e){return new r.default(e)},e.combinator=function(e){return new n.default(e)},e.comment=function(e){return new i.default(e)},e.id=function(e){return new o.default(e)},e.nesting=function(e){return new s.default(e)},e.pseudo=function(e){return new a.default(e)},e.root=function(e){return new l.default(e)},e.selector=function(e){return new c.default(e)},e.string=function(e){return new u.default(e)},e.tag=function(e){return new d.default(e)},e.universal=function(e){return new p.default(e)}})),rn=f((e=>{g(),e.__esModule=!0,e.isComment=e.isCombinator=e.isClassName=e.isAttribute=void 0,e.isContainer=function(e){return!(!i(e)||!e.walk)},e.isIdentifier=void 0,e.isNamespace=function(e){return s(e)||y(e)},e.isNesting=void 0,e.isNode=i,e.isPseudo=void 0,e.isPseudoClass=function(e){return p(e)&&!v(e)},e.isPseudoElement=v,e.isUniversal=e.isTag=e.isString=e.isSelector=e.isRoot=void 0;var t,r=Pr(),n=((t={})[r.ATTRIBUTE]=!0,t[r.CLASS]=!0,t[r.COMBINATOR]=!0,t[r.COMMENT]=!0,t[r.ID]=!0,t[r.NESTING]=!0,t[r.PSEUDO]=!0,t[r.ROOT]=!0,t[r.SELECTOR]=!0,t[r.STRING]=!0,t[r.TAG]=!0,t[r.UNIVERSAL]=!0,t);function i(e){return"object"==typeof e&&n[e.type]}function o(e,t){return i(t)&&t.type===e}var s=o.bind(null,r.ATTRIBUTE);e.isAttribute=s;var a=o.bind(null,r.CLASS);e.isClassName=a;var l=o.bind(null,r.COMBINATOR);e.isCombinator=l;var c=o.bind(null,r.COMMENT);e.isComment=c;var u=o.bind(null,r.ID);e.isIdentifier=u;var d=o.bind(null,r.NESTING);e.isNesting=d;var p=o.bind(null,r.PSEUDO);e.isPseudo=p;var f=o.bind(null,r.ROOT);e.isRoot=f;var h=o.bind(null,r.SELECTOR);e.isSelector=h;var m=o.bind(null,r.STRING);e.isString=m;var y=o.bind(null,r.TAG);e.isTag=y;var b=o.bind(null,r.UNIVERSAL);function v(e){return p(e)&&e.value&&(e.value.startsWith("::")||":before"===e.value.toLowerCase()||":after"===e.value.toLowerCase()||":first-letter"===e.value.toLowerCase()||":first-line"===e.value.toLowerCase())}e.isUniversal=b})),nn=f((e=>{g(),e.__esModule=!0;var t=Pr();Object.keys(t).forEach((function(r){"default"===r||"__esModule"===r||r in e&&e[r]===t[r]||(e[r]=t[r])}));var r=tn();Object.keys(r).forEach((function(t){"default"===t||"__esModule"===t||t in e&&e[t]===r[t]||(e[t]=r[t])}));var n=rn();Object.keys(n).forEach((function(t){"default"===t||"__esModule"===t||t in e&&e[t]===n[t]||(e[t]=n[t])}))})),on=f(((e,t)=>{g(),e.__esModule=!0,e.default=void 0;var r,n=(r=en())&&r.__esModule?r:{default:r},i=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=o(t);if(r&&r.has(e))return r.get(e);var n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=i?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(n,s,a):n[s]=e[s]}return n.default=e,r&&r.set(e,n),n}(nn());function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(o=function(e){return e?r:t})(e)}var s=function(e){return new n.default(e)};Object.assign(s,i),delete s.__esModule;var a=s;e.default=a,t.exports=e.default}));function sn(e){return["fontSize","outline"].includes(e)?e=>("function"==typeof e&&(e=e({})),Array.isArray(e)&&(e=e[0]),e):"fontFamily"===e?e=>{"function"==typeof e&&(e=e({}));let t=Array.isArray(e)&&Q(e[1])?e[0]:e;return Array.isArray(t)?t.join(", "):t}:["boxShadow","transitionProperty","transitionDuration","transitionDelay","transitionTimingFunction","backgroundImage","backgroundSize","backgroundColor","cursor","animation"].includes(e)?e=>("function"==typeof e&&(e=e({})),Array.isArray(e)&&(e=e.join(", ")),e):["gridTemplateColumns","gridTemplateRows","objectPosition"].includes(e)?e=>("function"==typeof e&&(e=e({})),"string"==typeof e&&(e=Wt.list.comma(e).join(" ")),e):(e,t={})=>("function"==typeof e&&(e=e(t)),e)}var an,ln,cn=p((()=>{g(),kr(),J()})),un=f(((e,t)=>{g();var{Rule:r,AtRule:n}=xr(),i=on();function o(e,t){let r;try{i((e=>{r=e})).processSync(e)}catch(r){throw e.includes(":")?t?t.error("Missed semicolon"):r:t?t.error(r.message):r}return r.at(0)}function s(e,t){let r=!1;return e.each((e=>{if("nesting"===e.type){let n=t.clone({});"&"!==e.value?e.replaceWith(o(e.value.replace("&",n.toString()))):e.replaceWith(n),r=!0}else"nodes"in e&&e.nodes&&s(e,t)&&(r=!0)})),r}function a(e,t){let r=[];return e.selectors.forEach((n=>{let a=o(n,e);t.selectors.forEach((e=>{if(!e)return;let n=o(e,t);s(n,a)||(n.prepend(i.combinator({value:" "})),n.prepend(a.clone({}))),r.push(n.toString())}))})),r}function l(e,t){let r=e.prev();for(t.after(e);r&&"comment"===r.type;){let e=r.prev();t.after(r),r=e}return e}function c(e,t,n){let i=new r({selector:e,nodes:[]});return i.append(t),n.after(i),i}function u(e,t){let r={};for(let t of e)r[t]=!0;if(t)for(let e of t)r[e.replace(/^@/,"")]=!0;return r}function d(e){let t=e[f];if(t){let r,i,o,s,a=e.nodes,l=-1,c=function(e){let t=[],r=e.parent;for(;r&&r instanceof n;)t.push(r),r=r.parent;return t}(e);if(c.forEach(((e,n)=>{if(t(e.name))r=e,l=n,o=s;else{let t=s;s=e.clone({nodes:[]}),t&&s.append(t),i=i||s}})),r?o?(i.append(a),r.after(o)):r.after(a):e.after(a),e.next()&&r){let t;c.slice(0,l+1).forEach(((r,n,i)=>{let o=t;t=r.clone({nodes:[]}),o&&t.append(o);let s=[],a=(i[n-1]||e).next();for(;a;)s.push(a),a=a.next();t.append(s)})),t&&(o||a[a.length-1]).after(t)}}else e.after(e.nodes);e.remove()}var p=Symbol("rootRuleMergeSel"),f=Symbol("rootRuleEscapes");var h=Symbol("hasRootRule");t.exports=(e={})=>{let t=u(["media","supports","layer","container"],e.bubble),n=function(e){return function t(r,n,i,o=i){let s=[];if(n.each((l=>{"rule"===l.type&&i?o&&(l.selectors=a(r,l)):"atrule"===l.type&&l.nodes?e[l.name]?t(r,l,o):!1!==n[p]&&s.push(l):s.push(l)})),i&&s.length){let e=r.clone({nodes:[]});for(let t of s)e.append(t);n.prepend(e)}}}(t),i=u(["document","font-face","keyframes","-webkit-keyframes","-moz-keyframes"],e.unwrap),o=(e.rootRuleName||"at-root").replace(/^@/,""),s=e.preserveEmpty;return{postcssPlugin:"postcss-nested",Once(e){e.walkAtRules(o,(t=>{(function(e){let{params:t}=e,{type:n,selector:i,escapes:o}=function(e){let t=(e=e.trim()).match(/^\((.*)\)$/);if(!t)return{type:"basic",selector:e};let r=t[1].match(/^(with(?:out)?):(.+)$/);if(r){let e="with"===r[1],t=Object.fromEntries(r[2].trim().split(/\s+/).map((e=>[e,!0])));if(e&&t.all)return{type:"noop"};let n=e=>!!t[e];return t.all?n=()=>!0:e&&(n=e=>"all"!==e&&!t[e]),{type:"withrules",escapes:n}}return{type:"unknown"}}(t);if("unknown"===n)throw e.error(`Unknown @${e.name} parameter ${JSON.stringify(t)}`);if("basic"===n&&i){let t=new r({selector:i,nodes:e.nodes});e.removeAll(),e.append(t)}e[f]=o,e[p]=o?!o("all"):"noop"===n})(t),e[h]=!0}))},Rule(e){let r=!1,u=e,d=!1,f=[];e.each((s=>{"rule"===s.type?(f.length&&(u=c(e.selector,f,u),f=[]),d=!0,r=!0,s.selectors=a(e,s),u=l(s,u)):"atrule"===s.type?(f.length&&(u=c(e.selector,f,u),f=[]),s.name===o?(r=!0,n(e,s,!0,s[p]),u=l(s,u)):t[s.name]?(d=!0,r=!0,n(e,s,!0),u=l(s,u)):i[s.name]?(d=!0,r=!0,n(e,s,!1),u=l(s,u)):d&&f.push(s)):"decl"===s.type&&d&&f.push(s)})),f.length&&(u=c(e.selector,f,u)),r&&!0!==s&&(e.raws.semicolon=!0,0===e.nodes.length&&e.remove())},RootExit(e){e[h]&&(e.walkAtRules(o,d),e[h]=!1)}}},t.exports.postcss=!0})),dn=f(((e,t)=>{g();var r=/-(\w|$)/g,n=(e,t)=>t.toUpperCase();t.exports=e=>"float"===(e=e.toLowerCase())?"cssFloat":e.startsWith("-ms-")?e.substr(1).replace(r,n):e.replace(r,n)})),pn=f(((e,t)=>{g();var r=dn(),n={boxFlex:!0,boxFlexGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,strokeDashoffset:!0,strokeOpacity:!0,strokeWidth:!0};function i(e){return void 0===e.nodes||o(e)}function o(e){let t,s={};return e.each((e=>{if("atrule"===e.type)t="@"+e.name,e.params&&(t+=" "+e.params),void 0===s[t]?s[t]=i(e):Array.isArray(s[t])?s[t].push(i(e)):s[t]=[s[t],i(e)];else if("rule"===e.type){let t=o(e);if(s[e.selector])for(let r in t)s[e.selector][r]=t[r];else s[e.selector]=t}else if("decl"===e.type){t="-"===e.prop[0]&&"-"===e.prop[1]||e.parent&&":export"===e.parent.selector?e.prop:r(e.prop);let i=e.value;!isNaN(e.value)&&n[t]&&(i=parseFloat(e.value)),e.important&&(i+=" !important"),void 0===s[t]?s[t]=i:Array.isArray(s[t])?s[t].push(i):s[t]=[s[t],i]}})),s}t.exports=o})),fn=f(((e,t)=>{g();var r=xr(),n=/\s*!important\s*$/i,i={"box-flex":!0,"box-flex-group":!0,"column-count":!0,flex:!0,"flex-grow":!0,"flex-positive":!0,"flex-shrink":!0,"flex-negative":!0,"font-weight":!0,"line-clamp":!0,"line-height":!0,opacity:!0,order:!0,orphans:!0,"tab-size":!0,widows:!0,"z-index":!0,zoom:!0,"fill-opacity":!0,"stroke-dashoffset":!0,"stroke-opacity":!0,"stroke-width":!0};function o(e,t,o){!1===o||null===o||(t.startsWith("--")||(t=function(e){return e.replace(/([A-Z])/g,"-$1").replace(/^ms-/,"-ms-").toLowerCase()}(t)),"number"==typeof o&&(0===o||i[t]?o=o.toString():o+="px"),"css-float"===t&&(t="float"),n.test(o)?(o=o.replace(n,""),e.push(r.decl({prop:t,value:o,important:!0}))):e.push(r.decl({prop:t,value:o})))}function s(e,t,n){let i=r.atRule({name:t[1],params:t[3]||""});"object"==typeof n&&(i.nodes=[],a(n,i)),e.push(i)}function a(e,t){let n,i,l;for(n in e)if(i=e[n],null!=i)if("@"===n[0]){let e=n.match(/@(\S+)(\s+([\W\w]*)\s*)?/);if(Array.isArray(i))for(let r of i)s(t,e,r);else s(t,e,i)}else if(Array.isArray(i))for(let e of i)o(t,n,e);else"object"==typeof i?(l=r.rule({selector:n}),a(i,l),t.push(l)):o(t,n,i)}t.exports=function(e){let t=r.root();return a(e,t),t}})),hn=f(((e,t)=>{g();var r=pn();t.exports=function(e){return console&&console.warn&&e.warnings().forEach((e=>{let t=e.plugin||"PostCSS";console.warn(t+": "+e.text)})),r(e.root)}})),mn=f(((e,t)=>{g();var r=xr(),n=hn(),i=fn();t.exports=function(e){let t=r(e);return async e=>{let r=await t.process(e,{parser:i,from:void 0});return n(r)}}})),gn=f(((e,t)=>{g();var r=xr(),n=hn(),i=fn();t.exports=function(e){let t=r(e);return e=>{let r=t.process(e,{parser:i,from:void 0});return n(r)}}})),yn=f(((e,t)=>{g();var r=pn(),n=fn(),i=mn(),o=gn();t.exports={objectify:r,parse:n,async:i,sync:o}})),bn=p((()=>{g(),an=m(yn()),ln=an.default,an.default.objectify,an.default.parse,an.default.async,an.default.sync}));function vn(e){return Array.isArray(e)?e.flatMap((e=>Wt([(0,wn.default)({bubble:["screen"]})]).process(e,{parser:ln}).root.nodes)):vn([e])}var wn,xn=p((()=>{g(),kr(),wn=m(un()),bn()}));function kn(e,t,r=!1){if(""===e)return t;let n="string"==typeof t?(0,Sn.default)().astSync(t):t;return n.walkClasses((t=>{let n=t.value,i=r&&n.startsWith("-");t.value=i?`-${e}${n.slice(1)}`:`${e}${n}`})),"string"==typeof t?n.toString():n}var Sn,Cn=p((()=>{g(),Sn=m(on())}));function An(e){let t=On.default.className();return t.value=e,Z(t?.raws?.value??t.value)}var On,En=p((()=>{g(),On=m(on()),te()}));function _n(e){return Z(`.${An(e)}`)}function Tn(e,t){return _n(Pn(e,t))}function Pn(e,t){return"DEFAULT"===t?e:"-"===t||"-DEFAULT"===t?`-${e}`:t.startsWith("-")?`-${e}${t}`:t.startsWith("/")?`${e}${t}`:`${e}-${t}`}var jn=p((()=>{g(),En(),te()}));function In(e,t=[[e,[e]]],{filterDefault:r=!1,...n}={}){let i=sn(e);return function({matchUtilities:o,theme:s}){for(let a of t)o((Array.isArray(a[0])?a:[a]).reduce(((e,[t,r])=>Object.assign(e,{[t]:e=>r.reduce(((t,r)=>Array.isArray(r)?Object.assign(t,{[r[0]]:r[1]}):Object.assign(t,{[r]:i(e)})),{})})),{}),{...n,values:r?Object.fromEntries(Object.entries(s(e)??{}).filter((([e])=>"DEFAULT"!==e))):s(e)})}}var Dn=p((()=>{g(),cn()}));function $n(e){return(e=Array.isArray(e)?e:[e]).map((e=>{let t=e.values.map((e=>void 0!==e.raw?e.raw:[e.min&&`(min-width: ${e.min})`,e.max&&`(max-width: ${e.max})`].filter(Boolean).join(" and ")));return e.not?`not all and ${t}`:t})).join(", ")}var Bn,Rn,Mn,Un,zn,Ln,Fn,Nn,Vn,Wn,qn,Gn,Yn,Hn=p((()=>{g()})),Qn=p((()=>{g(),Bn=new Set(["normal","reverse","alternate","alternate-reverse"]),Rn=new Set(["running","paused"]),Mn=new Set(["none","forwards","backwards","both"]),Un=new Set(["infinite"]),zn=new Set(["linear","ease","ease-in","ease-out","ease-in-out","step-start","step-end"]),Ln=["cubic-bezier","steps"],Fn=/\,(?![^(]*\))/g,Nn=/\ +(?![^(]*\))/g,Vn=/^(-?[\d.]+m?s)$/,Wn=/^(\d+)$/})),Jn=p((()=>{g(),Gn=qn=e=>Object.assign({},...Object.entries(e??{}).flatMap((([e,t])=>"object"==typeof t?Object.entries(qn(t)).map((([t,r])=>({[e+("DEFAULT"===t?"":`-${t}`)]:r}))):[{[`${e}`]:t}])))})),Xn=p((()=>{Yn="3.4.5"}));function Kn(e,t=!0){return Array.isArray(e)?e.map((e=>{if(t&&Array.isArray(e))throw new Error("The tuple syntax is not supported for `screens`.");if("string"==typeof e)return{name:e.toString(),not:!1,values:[{min:e,max:void 0}]};let[r,n]=e;return r=r.toString(),"string"==typeof n?{name:r,not:!1,values:[{min:n,max:void 0}]}:Array.isArray(n)?{name:r,not:!1,values:n.map((e=>ti(e)))}:{name:r,not:!1,values:[ti(n)]}})):Kn(Object.entries(e??{}),!1)}function Zn(e){return 1!==e.values.length?{result:!1,reason:"multiple-values"}:void 0!==e.values[0].raw?{result:!1,reason:"raw-values"}:void 0!==e.values[0].min&&void 0!==e.values[0].max?{result:!1,reason:"min-and-max"}:{result:!0,reason:null}}function ei(e,t){return"object"==typeof e?e:{name:"arbitrary-screen",values:[{[t]:e}]}}function ti({"min-width":e,min:t=e,max:r,raw:n}={}){return{min:t,max:r,raw:n}}var ri=p((()=>{g()}));function ni(e,t){e.walkDecls((e=>{if(t.includes(e.prop))e.remove();else for(let r of t)e.value.includes(`/ var(${r})`)&&(e.value=e.value.replace(`/ var(${r})`,""))}))}var ii,oi,si,ai,li,ci=p((()=>{g()})),ui=p((()=>{g(),y(),Rt(),kr(),Dn(),Hn(),En(),Qn(),Jn(),ge(),ht(),J(),cn(),Xn(),$(),ri(),Ce(),ci(),Y(),Qe(),No(),ii={childVariant:({addVariant:e})=>{e("*","& > *")},pseudoElementVariants:({addVariant:e})=>{e("first-letter","&::first-letter"),e("first-line","&::first-line"),e("marker",[({container:e})=>(ni(e,["--tw-text-opacity"]),"& *::marker"),({container:e})=>(ni(e,["--tw-text-opacity"]),"&::marker")]),e("selection",["& *::selection","&::selection"]),e("file","&::file-selector-button"),e("placeholder","&::placeholder"),e("backdrop","&::backdrop"),e("before",(({container:e})=>(e.walkRules((e=>{let t=!1;e.walkDecls("content",(()=>{t=!0})),t||e.prepend(Wt.decl({prop:"content",value:"var(--tw-content)"}))})),"&::before"))),e("after",(({container:e})=>(e.walkRules((e=>{let t=!1;e.walkDecls("content",(()=>{t=!0})),t||e.prepend(Wt.decl({prop:"content",value:"var(--tw-content)"}))})),"&::after")))},pseudoClassVariants:({addVariant:e,matchVariant:t,config:r,prefix:n})=>{let i=[["first","&:first-child"],["last","&:last-child"],["only","&:only-child"],["odd","&:nth-child(odd)"],["even","&:nth-child(even)"],"first-of-type","last-of-type","only-of-type",["visited",({container:e})=>(ni(e,["--tw-text-opacity","--tw-border-opacity","--tw-bg-opacity"]),"&:visited")],"target",["open","&[open]"],"default","checked","indeterminate","placeholder-shown","autofill","optional","required","valid","invalid","in-range","out-of-range","read-only","empty","focus-within",["hover",V(r(),"hoverOnlyWhenSupported")?"@media (hover: hover) and (pointer: fine) { &:hover }":"&:hover"],"focus","focus-visible","active","enabled","disabled"].map((e=>Array.isArray(e)?e:[e,`&:${e}`]));for(let[t,r]of i)e(t,(e=>"function"==typeof r?r(e):r));let o={group:(e,{modifier:t})=>t?[`:merge(${n(".group")}\\/${An(t)})`," &"]:[`:merge(${n(".group")})`," &"],peer:(e,{modifier:t})=>t?[`:merge(${n(".peer")}\\/${An(t)})`," ~ &"]:[`:merge(${n(".peer")})`," ~ &"]};for(let[e,r]of Object.entries(o))t(e,((e="",t)=>{let n=Oe("function"==typeof e?e(t):e);n.includes("&")||(n="&"+n);let[i,o]=r("",t),s=null,a=null,l=0;for(let e=0;e{e("ltr",'&:where([dir="ltr"], [dir="ltr"] *)'),e("rtl",'&:where([dir="rtl"], [dir="rtl"] *)')},reducedMotionVariants:({addVariant:e})=>{e("motion-safe","@media (prefers-reduced-motion: no-preference)"),e("motion-reduce","@media (prefers-reduced-motion: reduce)")},darkVariants:({config:e,addVariant:t})=>{let[r,n=".dark"]=[].concat(e("darkMode","media"));if(!1===r&&(r="media",D.warn("darkmode-false",["The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.","Change `darkMode` to `media` or remove it entirely.","https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration"])),"variant"===r){let e;if(Array.isArray(n)||"function"==typeof n?e=n:"string"==typeof n&&(e=[n]),Array.isArray(e))for(let t of e)".dark"===t?(r=!1,D.warn("darkmode-variant-without-selector",["When using `variant` for `darkMode`, you must provide a selector.",'Example: `darkMode: ["variant", ".your-selector &"]`'])):t.includes("&")||(r=!1,D.warn("darkmode-variant-without-ampersand",["When using `variant` for `darkMode`, your selector must contain `&`.",'Example `darkMode: ["variant", ".your-selector &"]`']));n=e}"selector"===r?t("dark",`&:where(${n}, ${n} *)`):"media"===r?t("dark","@media (prefers-color-scheme: dark)"):"variant"===r?t("dark",n):"class"===r&&t("dark",`&:is(${n} *)`)},printVariant:({addVariant:e})=>{e("print","@media print")},screenVariants:({theme:e,addVariant:t,matchVariant:r})=>{let n=e("screens")??{},i=Object.values(n).every((e=>"string"==typeof e)),o=Kn(e("screens")),s=new Set([]);function a(e){void 0!==e&&s.add(function(e){return e.match(/(\D+)$/)?.[1]??"(none)"}(e))}for(let e of o)for(let t of e.values)a(t.min),a(t.max);let l=s.size<=1;function c(e){return(t,r)=>function(e,t,r){let n=ei(t,e),i=ei(r,e),o=Zn(n),s=Zn(i);if("multiple-values"===o.reason||"multiple-values"===s.reason)throw new Error("Attempted to sort a screen with multiple values. This should never happen. Please open a bug report.");if("raw-values"===o.reason||"raw-values"===s.reason)throw new Error("Attempted to sort a screen with raw values. This should never happen. Please open a bug report.");if("min-and-max"===o.reason||"min-and-max"===s.reason)throw new Error("Attempted to sort a screen with both min and max values. This should never happen. Please open a bug report.");let{min:a,max:l}=n.values[0],{min:c,max:u}=i.values[0];t.not&&([a,l]=[l,a]),r.not&&([c,u]=[u,c]),a=void 0===a?a:parseFloat(a),l=void 0===l?l:parseFloat(l),c=void 0===c?c:parseFloat(c),u=void 0===u?u:parseFloat(u);let[d,p]="min"===e?[a,c]:[u,l];return d-p}(e,t.value,r.value)}let u=c("max"),d=c("min");function p(e){return t=>i?l?"string"!=typeof t||function(e){return a(e),1===s.size}(t)?[`@media ${$n(ei(t,e))}`]:(D.warn("minmax-have-mixed-units",["The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units."]),[]):(D.warn("mixed-screen-units",["The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units."]),[]):(D.warn("complex-screen-config",["The `min-*` and `max-*` variants are not supported with a `screens` configuration containing objects."]),[])}r("max",p("max"),{sort:u,values:i?("max",Object.fromEntries(o.filter((e=>Zn(e).result)).map((e=>{let{min:t,max:r}=e.values[0];return void 0!==r?e:void 0!==t?{...e,not:!e.not}:void 0})).map((e=>[e.name,e])))):{}});let f="min-screens";for(let e of o)t(e.name,`@media ${$n(e)}`,{id:f,sort:i&&l?d:void 0,value:e});r("min",p("min"),{id:f,sort:d})},supportsVariants:({matchVariant:e,theme:t})=>{e("supports",((e="")=>{let t=Oe(e),r=/^\w*\s*\(/.test(t);return t=r?t.replace(/\b(and|or|not)\b/g," $1 "):t,r||(t.includes(":")||(t=`${t}: var(--tw)`),t.startsWith("(")&&t.endsWith(")")||(t=`(${t})`)),`@supports ${t}`}),{values:t("supports")??{}})},hasVariants:({matchVariant:e,prefix:t})=>{e("has",(e=>`&:has(${Oe(e)})`),{values:{},[Bo]:{respectPrefix:!1}}),e("group-has",((e,{modifier:r})=>r?`:merge(${t(".group")}\\/${r}):has(${Oe(e)}) &`:`:merge(${t(".group")}):has(${Oe(e)}) &`),{values:{},[Bo]:{respectPrefix:!1}}),e("peer-has",((e,{modifier:r})=>r?`:merge(${t(".peer")}\\/${r}):has(${Oe(e)}) ~ &`:`:merge(${t(".peer")}):has(${Oe(e)}) ~ &`),{values:{},[Bo]:{respectPrefix:!1}})},ariaVariants:({matchVariant:e,theme:t})=>{e("aria",(e=>`&[aria-${Oe(e)}]`),{values:t("aria")??{}}),e("group-aria",((e,{modifier:t})=>t?`:merge(.group\\/${t})[aria-${Oe(e)}] &`:`:merge(.group)[aria-${Oe(e)}] &`),{values:t("aria")??{}}),e("peer-aria",((e,{modifier:t})=>t?`:merge(.peer\\/${t})[aria-${Oe(e)}] ~ &`:`:merge(.peer)[aria-${Oe(e)}] ~ &`),{values:t("aria")??{}})},dataVariants:({matchVariant:e,theme:t})=>{e("data",(e=>`&[data-${Oe(e)}]`),{values:t("data")??{}}),e("group-data",((e,{modifier:t})=>t?`:merge(.group\\/${t})[data-${Oe(e)}] &`:`:merge(.group)[data-${Oe(e)}] &`),{values:t("data")??{}}),e("peer-data",((e,{modifier:t})=>t?`:merge(.peer\\/${t})[data-${Oe(e)}] ~ &`:`:merge(.peer)[data-${Oe(e)}] ~ &`),{values:t("data")??{}})},orientationVariants:({addVariant:e})=>{e("portrait","@media (orientation: portrait)"),e("landscape","@media (orientation: landscape)")},prefersContrastVariants:({addVariant:e})=>{e("contrast-more","@media (prefers-contrast: more)"),e("contrast-less","@media (prefers-contrast: less)")},forcedColorsVariants:({addVariant:e})=>{e("forced-colors","@media (forced-colors: active)")}},oi=["translate(var(--tw-translate-x), var(--tw-translate-y))","rotate(var(--tw-rotate))","skewX(var(--tw-skew-x))","skewY(var(--tw-skew-y))","scaleX(var(--tw-scale-x))","scaleY(var(--tw-scale-y))"].join(" "),si=["var(--tw-blur)","var(--tw-brightness)","var(--tw-contrast)","var(--tw-grayscale)","var(--tw-hue-rotate)","var(--tw-invert)","var(--tw-saturate)","var(--tw-sepia)","var(--tw-drop-shadow)"].join(" "),ai=["var(--tw-backdrop-blur)","var(--tw-backdrop-brightness)","var(--tw-backdrop-contrast)","var(--tw-backdrop-grayscale)","var(--tw-backdrop-hue-rotate)","var(--tw-backdrop-invert)","var(--tw-backdrop-opacity)","var(--tw-backdrop-saturate)","var(--tw-backdrop-sepia)"].join(" "),li={preflight:({addBase:e})=>{let t=Wt.parse("*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:theme('borderColor.DEFAULT', currentColor)}::after,::before{--tw-content:''}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:theme('fontFamily.sans', ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");font-feature-settings:theme('fontFamily.sans[1].fontFeatureSettings', normal);font-variation-settings:theme('fontFamily.sans[1].fontVariationSettings', normal);-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-feature-settings:theme('fontFamily.mono[1].fontFeatureSettings', normal);font-variation-settings:theme('fontFamily.mono[1].fontVariationSettings', normal);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:theme('colors.gray.4', #9ca3af)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}");e([Wt.comment({text:`! tailwindcss v${Yn} | MIT License | https://tailwindcss.com`}),...t.nodes])},container:function({addComponents:e,theme:t}){let r=Kn(t("container.screens",t("screens"))),n=function(e=[]){return e.flatMap((e=>e.values.map((e=>e.min)))).filter((e=>void 0!==e))}(r),i=function(e,t,r){if(void 0===r)return[];if("object"!=typeof r||null===r)return[{screen:"DEFAULT",minWidth:0,padding:r}];let n=[];r.DEFAULT&&n.push({screen:"DEFAULT",minWidth:0,padding:r.DEFAULT});for(let i of e)for(let e of t)for(let{min:t}of e.values)t===i&&n.push({minWidth:i,padding:r[e.name]});return n}(n,r,t("container.padding")),o=e=>{let t=i.find((t=>t.minWidth===e));return t?{paddingRight:t.padding,paddingLeft:t.padding}:{}},s=Array.from(new Set(n.slice().sort(((e,t)=>parseInt(e)-parseInt(t))))).map((e=>({[`@media (min-width: ${e})`]:{".container":{"max-width":e,...o(e)}}})));e([{".container":Object.assign({width:"100%"},t("container.center",!1)?{marginRight:"auto",marginLeft:"auto"}:{},o(0))},...s])},accessibility:({addUtilities:e})=>{e({".sr-only":{position:"absolute",width:"1px",height:"1px",padding:"0",margin:"-1px",overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0"},".not-sr-only":{position:"static",width:"auto",height:"auto",padding:"0",margin:"0",overflow:"visible",clip:"auto",whiteSpace:"normal"}})},pointerEvents:({addUtilities:e})=>{e({".pointer-events-none":{"pointer-events":"none"},".pointer-events-auto":{"pointer-events":"auto"}})},visibility:({addUtilities:e})=>{e({".visible":{visibility:"visible"},".invisible":{visibility:"hidden"},".collapse":{visibility:"collapse"}})},position:({addUtilities:e})=>{e({".static":{position:"static"},".fixed":{position:"fixed"},".absolute":{position:"absolute"},".relative":{position:"relative"},".sticky":{position:"sticky"}})},inset:In("inset",[["inset",["inset"]],[["inset-x",["left","right"]],["inset-y",["top","bottom"]]],[["start",["inset-inline-start"]],["end",["inset-inline-end"]],["top",["top"]],["right",["right"]],["bottom",["bottom"]],["left",["left"]]]],{supportsNegativeValues:!0}),isolation:({addUtilities:e})=>{e({".isolate":{isolation:"isolate"},".isolation-auto":{isolation:"auto"}})},zIndex:In("zIndex",[["z",["zIndex"]]],{supportsNegativeValues:!0}),order:In("order",void 0,{supportsNegativeValues:!0}),gridColumn:In("gridColumn",[["col",["gridColumn"]]]),gridColumnStart:In("gridColumnStart",[["col-start",["gridColumnStart"]]],{supportsNegativeValues:!0}),gridColumnEnd:In("gridColumnEnd",[["col-end",["gridColumnEnd"]]],{supportsNegativeValues:!0}),gridRow:In("gridRow",[["row",["gridRow"]]]),gridRowStart:In("gridRowStart",[["row-start",["gridRowStart"]]],{supportsNegativeValues:!0}),gridRowEnd:In("gridRowEnd",[["row-end",["gridRowEnd"]]],{supportsNegativeValues:!0}),float:({addUtilities:e})=>{e({".float-start":{float:"inline-start"},".float-end":{float:"inline-end"},".float-right":{float:"right"},".float-left":{float:"left"},".float-none":{float:"none"}})},clear:({addUtilities:e})=>{e({".clear-start":{clear:"inline-start"},".clear-end":{clear:"inline-end"},".clear-left":{clear:"left"},".clear-right":{clear:"right"},".clear-both":{clear:"both"},".clear-none":{clear:"none"}})},margin:In("margin",[["m",["margin"]],[["mx",["margin-left","margin-right"]],["my",["margin-top","margin-bottom"]]],[["ms",["margin-inline-start"]],["me",["margin-inline-end"]],["mt",["margin-top"]],["mr",["margin-right"]],["mb",["margin-bottom"]],["ml",["margin-left"]]]],{supportsNegativeValues:!0}),boxSizing:({addUtilities:e})=>{e({".box-border":{"box-sizing":"border-box"},".box-content":{"box-sizing":"content-box"}})},lineClamp:({matchUtilities:e,addUtilities:t,theme:r})=>{e({"line-clamp":e=>({overflow:"hidden",display:"-webkit-box","-webkit-box-orient":"vertical","-webkit-line-clamp":`${e}`})},{values:r("lineClamp")}),t({".line-clamp-none":{overflow:"visible",display:"block","-webkit-box-orient":"horizontal","-webkit-line-clamp":"none"}})},display:({addUtilities:e})=>{e({".block":{display:"block"},".inline-block":{display:"inline-block"},".inline":{display:"inline"},".flex":{display:"flex"},".inline-flex":{display:"inline-flex"},".table":{display:"table"},".inline-table":{display:"inline-table"},".table-caption":{display:"table-caption"},".table-cell":{display:"table-cell"},".table-column":{display:"table-column"},".table-column-group":{display:"table-column-group"},".table-footer-group":{display:"table-footer-group"},".table-header-group":{display:"table-header-group"},".table-row-group":{display:"table-row-group"},".table-row":{display:"table-row"},".flow-root":{display:"flow-root"},".grid":{display:"grid"},".inline-grid":{display:"inline-grid"},".contents":{display:"contents"},".list-item":{display:"list-item"},".hidden":{display:"none"}})},aspectRatio:In("aspectRatio",[["aspect",["aspect-ratio"]]]),size:In("size",[["size",["width","height"]]]),height:In("height",[["h",["height"]]]),maxHeight:In("maxHeight",[["max-h",["maxHeight"]]]),minHeight:In("minHeight",[["min-h",["minHeight"]]]),width:In("width",[["w",["width"]]]),minWidth:In("minWidth",[["min-w",["minWidth"]]]),maxWidth:In("maxWidth",[["max-w",["maxWidth"]]]),flex:In("flex"),flexShrink:In("flexShrink",[["flex-shrink",["flex-shrink"]],["shrink",["flex-shrink"]]]),flexGrow:In("flexGrow",[["flex-grow",["flex-grow"]],["grow",["flex-grow"]]]),flexBasis:In("flexBasis",[["basis",["flex-basis"]]]),tableLayout:({addUtilities:e})=>{e({".table-auto":{"table-layout":"auto"},".table-fixed":{"table-layout":"fixed"}})},captionSide:({addUtilities:e})=>{e({".caption-top":{"caption-side":"top"},".caption-bottom":{"caption-side":"bottom"}})},borderCollapse:({addUtilities:e})=>{e({".border-collapse":{"border-collapse":"collapse"},".border-separate":{"border-collapse":"separate"}})},borderSpacing:({addDefaults:e,matchUtilities:t,theme:r})=>{e("border-spacing",{"--tw-border-spacing-x":0,"--tw-border-spacing-y":0}),t({"border-spacing":e=>({"--tw-border-spacing-x":e,"--tw-border-spacing-y":e,"@defaults border-spacing":{},"border-spacing":"var(--tw-border-spacing-x) var(--tw-border-spacing-y)"}),"border-spacing-x":e=>({"--tw-border-spacing-x":e,"@defaults border-spacing":{},"border-spacing":"var(--tw-border-spacing-x) var(--tw-border-spacing-y)"}),"border-spacing-y":e=>({"--tw-border-spacing-y":e,"@defaults border-spacing":{},"border-spacing":"var(--tw-border-spacing-x) var(--tw-border-spacing-y)"})},{values:r("borderSpacing")})},transformOrigin:In("transformOrigin",[["origin",["transformOrigin"]]]),translate:In("translate",[[["translate-x",[["@defaults transform",{}],"--tw-translate-x",["transform",oi]]],["translate-y",[["@defaults transform",{}],"--tw-translate-y",["transform",oi]]]]],{supportsNegativeValues:!0}),rotate:In("rotate",[["rotate",[["@defaults transform",{}],"--tw-rotate",["transform",oi]]]],{supportsNegativeValues:!0}),skew:In("skew",[[["skew-x",[["@defaults transform",{}],"--tw-skew-x",["transform",oi]]],["skew-y",[["@defaults transform",{}],"--tw-skew-y",["transform",oi]]]]],{supportsNegativeValues:!0}),scale:In("scale",[["scale",[["@defaults transform",{}],"--tw-scale-x","--tw-scale-y",["transform",oi]]],[["scale-x",[["@defaults transform",{}],"--tw-scale-x",["transform",oi]]],["scale-y",[["@defaults transform",{}],"--tw-scale-y",["transform",oi]]]]],{supportsNegativeValues:!0}),transform:({addDefaults:e,addUtilities:t})=>{e("transform",{"--tw-translate-x":"0","--tw-translate-y":"0","--tw-rotate":"0","--tw-skew-x":"0","--tw-skew-y":"0","--tw-scale-x":"1","--tw-scale-y":"1"}),t({".transform":{"@defaults transform":{},transform:oi},".transform-cpu":{transform:oi},".transform-gpu":{transform:oi.replace("translate(var(--tw-translate-x), var(--tw-translate-y))","translate3d(var(--tw-translate-x), var(--tw-translate-y), 0)")},".transform-none":{transform:"none"}})},animation:({matchUtilities:e,theme:t,config:r})=>{let n=e=>An(r("prefix")+e),i=Object.fromEntries(Object.entries(t("keyframes")??{}).map((([e,t])=>[e,{[`@keyframes ${n(e)}`]:t}])));e({animate:e=>{let t=function(e){return e.split(Fn).map((e=>{let t=e.trim(),r={value:t},n=t.split(Nn),i=new Set;for(let e of n)!i.has("DIRECTIONS")&&Bn.has(e)?(r.direction=e,i.add("DIRECTIONS")):!i.has("PLAY_STATES")&&Rn.has(e)?(r.playState=e,i.add("PLAY_STATES")):!i.has("FILL_MODES")&&Mn.has(e)?(r.fillMode=e,i.add("FILL_MODES")):i.has("ITERATION_COUNTS")||!Un.has(e)&&!Wn.test(e)?!i.has("TIMING_FUNCTION")&&zn.has(e)||!i.has("TIMING_FUNCTION")&&Ln.some((t=>e.startsWith(`${t}(`)))?(r.timingFunction=e,i.add("TIMING_FUNCTION")):!i.has("DURATION")&&Vn.test(e)?(r.duration=e,i.add("DURATION")):!i.has("DELAY")&&Vn.test(e)?(r.delay=e,i.add("DELAY")):i.has("NAME")?(r.unknown||(r.unknown=[]),r.unknown.push(e)):(r.name=e,i.add("NAME")):(r.iterationCount=e,i.add("ITERATION_COUNTS"));return r}))}(e);return[...t.flatMap((e=>i[e.name])),{animation:t.map((({name:e,value:t})=>void 0===e||void 0===i[e]?t:t.replace(e,n(e)))).join(", ")}]}},{values:t("animation")})},cursor:In("cursor"),touchAction:({addDefaults:e,addUtilities:t})=>{e("touch-action",{"--tw-pan-x":" ","--tw-pan-y":" ","--tw-pinch-zoom":" "});let r="var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom)";t({".touch-auto":{"touch-action":"auto"},".touch-none":{"touch-action":"none"},".touch-pan-x":{"@defaults touch-action":{},"--tw-pan-x":"pan-x","touch-action":r},".touch-pan-left":{"@defaults touch-action":{},"--tw-pan-x":"pan-left","touch-action":r},".touch-pan-right":{"@defaults touch-action":{},"--tw-pan-x":"pan-right","touch-action":r},".touch-pan-y":{"@defaults touch-action":{},"--tw-pan-y":"pan-y","touch-action":r},".touch-pan-up":{"@defaults touch-action":{},"--tw-pan-y":"pan-up","touch-action":r},".touch-pan-down":{"@defaults touch-action":{},"--tw-pan-y":"pan-down","touch-action":r},".touch-pinch-zoom":{"@defaults touch-action":{},"--tw-pinch-zoom":"pinch-zoom","touch-action":r},".touch-manipulation":{"touch-action":"manipulation"}})},userSelect:({addUtilities:e})=>{e({".select-none":{"user-select":"none"},".select-text":{"user-select":"text"},".select-all":{"user-select":"all"},".select-auto":{"user-select":"auto"}})},resize:({addUtilities:e})=>{e({".resize-none":{resize:"none"},".resize-y":{resize:"vertical"},".resize-x":{resize:"horizontal"},".resize":{resize:"both"}})},scrollSnapType:({addDefaults:e,addUtilities:t})=>{e("scroll-snap-type",{"--tw-scroll-snap-strictness":"proximity"}),t({".snap-none":{"scroll-snap-type":"none"},".snap-x":{"@defaults scroll-snap-type":{},"scroll-snap-type":"x var(--tw-scroll-snap-strictness)"},".snap-y":{"@defaults scroll-snap-type":{},"scroll-snap-type":"y var(--tw-scroll-snap-strictness)"},".snap-both":{"@defaults scroll-snap-type":{},"scroll-snap-type":"both var(--tw-scroll-snap-strictness)"},".snap-mandatory":{"--tw-scroll-snap-strictness":"mandatory"},".snap-proximity":{"--tw-scroll-snap-strictness":"proximity"}})},scrollSnapAlign:({addUtilities:e})=>{e({".snap-start":{"scroll-snap-align":"start"},".snap-end":{"scroll-snap-align":"end"},".snap-center":{"scroll-snap-align":"center"},".snap-align-none":{"scroll-snap-align":"none"}})},scrollSnapStop:({addUtilities:e})=>{e({".snap-normal":{"scroll-snap-stop":"normal"},".snap-always":{"scroll-snap-stop":"always"}})},scrollMargin:In("scrollMargin",[["scroll-m",["scroll-margin"]],[["scroll-mx",["scroll-margin-left","scroll-margin-right"]],["scroll-my",["scroll-margin-top","scroll-margin-bottom"]]],[["scroll-ms",["scroll-margin-inline-start"]],["scroll-me",["scroll-margin-inline-end"]],["scroll-mt",["scroll-margin-top"]],["scroll-mr",["scroll-margin-right"]],["scroll-mb",["scroll-margin-bottom"]],["scroll-ml",["scroll-margin-left"]]]],{supportsNegativeValues:!0}),scrollPadding:In("scrollPadding",[["scroll-p",["scroll-padding"]],[["scroll-px",["scroll-padding-left","scroll-padding-right"]],["scroll-py",["scroll-padding-top","scroll-padding-bottom"]]],[["scroll-ps",["scroll-padding-inline-start"]],["scroll-pe",["scroll-padding-inline-end"]],["scroll-pt",["scroll-padding-top"]],["scroll-pr",["scroll-padding-right"]],["scroll-pb",["scroll-padding-bottom"]],["scroll-pl",["scroll-padding-left"]]]]),listStylePosition:({addUtilities:e})=>{e({".list-inside":{"list-style-position":"inside"},".list-outside":{"list-style-position":"outside"}})},listStyleType:In("listStyleType",[["list",["listStyleType"]]]),listStyleImage:In("listStyleImage",[["list-image",["listStyleImage"]]]),appearance:({addUtilities:e})=>{e({".appearance-none":{appearance:"none"},".appearance-auto":{appearance:"auto"}})},columns:In("columns",[["columns",["columns"]]]),breakBefore:({addUtilities:e})=>{e({".break-before-auto":{"break-before":"auto"},".break-before-avoid":{"break-before":"avoid"},".break-before-all":{"break-before":"all"},".break-before-avoid-page":{"break-before":"avoid-page"},".break-before-page":{"break-before":"page"},".break-before-left":{"break-before":"left"},".break-before-right":{"break-before":"right"},".break-before-column":{"break-before":"column"}})},breakInside:({addUtilities:e})=>{e({".break-inside-auto":{"break-inside":"auto"},".break-inside-avoid":{"break-inside":"avoid"},".break-inside-avoid-page":{"break-inside":"avoid-page"},".break-inside-avoid-column":{"break-inside":"avoid-column"}})},breakAfter:({addUtilities:e})=>{e({".break-after-auto":{"break-after":"auto"},".break-after-avoid":{"break-after":"avoid"},".break-after-all":{"break-after":"all"},".break-after-avoid-page":{"break-after":"avoid-page"},".break-after-page":{"break-after":"page"},".break-after-left":{"break-after":"left"},".break-after-right":{"break-after":"right"},".break-after-column":{"break-after":"column"}})},gridAutoColumns:In("gridAutoColumns",[["auto-cols",["gridAutoColumns"]]]),gridAutoFlow:({addUtilities:e})=>{e({".grid-flow-row":{gridAutoFlow:"row"},".grid-flow-col":{gridAutoFlow:"column"},".grid-flow-dense":{gridAutoFlow:"dense"},".grid-flow-row-dense":{gridAutoFlow:"row dense"},".grid-flow-col-dense":{gridAutoFlow:"column dense"}})},gridAutoRows:In("gridAutoRows",[["auto-rows",["gridAutoRows"]]]),gridTemplateColumns:In("gridTemplateColumns",[["grid-cols",["gridTemplateColumns"]]]),gridTemplateRows:In("gridTemplateRows",[["grid-rows",["gridTemplateRows"]]]),flexDirection:({addUtilities:e})=>{e({".flex-row":{"flex-direction":"row"},".flex-row-reverse":{"flex-direction":"row-reverse"},".flex-col":{"flex-direction":"column"},".flex-col-reverse":{"flex-direction":"column-reverse"}})},flexWrap:({addUtilities:e})=>{e({".flex-wrap":{"flex-wrap":"wrap"},".flex-wrap-reverse":{"flex-wrap":"wrap-reverse"},".flex-nowrap":{"flex-wrap":"nowrap"}})},placeContent:({addUtilities:e})=>{e({".place-content-center":{"place-content":"center"},".place-content-start":{"place-content":"start"},".place-content-end":{"place-content":"end"},".place-content-between":{"place-content":"space-between"},".place-content-around":{"place-content":"space-around"},".place-content-evenly":{"place-content":"space-evenly"},".place-content-baseline":{"place-content":"baseline"},".place-content-stretch":{"place-content":"stretch"}})},placeItems:({addUtilities:e})=>{e({".place-items-start":{"place-items":"start"},".place-items-end":{"place-items":"end"},".place-items-center":{"place-items":"center"},".place-items-baseline":{"place-items":"baseline"},".place-items-stretch":{"place-items":"stretch"}})},alignContent:({addUtilities:e})=>{e({".content-normal":{"align-content":"normal"},".content-center":{"align-content":"center"},".content-start":{"align-content":"flex-start"},".content-end":{"align-content":"flex-end"},".content-between":{"align-content":"space-between"},".content-around":{"align-content":"space-around"},".content-evenly":{"align-content":"space-evenly"},".content-baseline":{"align-content":"baseline"},".content-stretch":{"align-content":"stretch"}})},alignItems:({addUtilities:e})=>{e({".items-start":{"align-items":"flex-start"},".items-end":{"align-items":"flex-end"},".items-center":{"align-items":"center"},".items-baseline":{"align-items":"baseline"},".items-stretch":{"align-items":"stretch"}})},justifyContent:({addUtilities:e})=>{e({".justify-normal":{"justify-content":"normal"},".justify-start":{"justify-content":"flex-start"},".justify-end":{"justify-content":"flex-end"},".justify-center":{"justify-content":"center"},".justify-between":{"justify-content":"space-between"},".justify-around":{"justify-content":"space-around"},".justify-evenly":{"justify-content":"space-evenly"},".justify-stretch":{"justify-content":"stretch"}})},justifyItems:({addUtilities:e})=>{e({".justify-items-start":{"justify-items":"start"},".justify-items-end":{"justify-items":"end"},".justify-items-center":{"justify-items":"center"},".justify-items-stretch":{"justify-items":"stretch"}})},gap:In("gap",[["gap",["gap"]],[["gap-x",["columnGap"]],["gap-y",["rowGap"]]]]),space:({matchUtilities:e,addUtilities:t,theme:r})=>{e({"space-x":e=>({"& > :not([hidden]) ~ :not([hidden])":{"--tw-space-x-reverse":"0","margin-right":`calc(${e="0"===e?"0px":e} * var(--tw-space-x-reverse))`,"margin-left":`calc(${e} * calc(1 - var(--tw-space-x-reverse)))`}}),"space-y":e=>({"& > :not([hidden]) ~ :not([hidden])":{"--tw-space-y-reverse":"0","margin-top":`calc(${e="0"===e?"0px":e} * calc(1 - var(--tw-space-y-reverse)))`,"margin-bottom":`calc(${e} * var(--tw-space-y-reverse))`}})},{values:r("space"),supportsNegativeValues:!0}),t({".space-y-reverse > :not([hidden]) ~ :not([hidden])":{"--tw-space-y-reverse":"1"},".space-x-reverse > :not([hidden]) ~ :not([hidden])":{"--tw-space-x-reverse":"1"}})},divideWidth:({matchUtilities:e,addUtilities:t,theme:r})=>{e({"divide-x":e=>({"& > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-x-reverse":"0","border-right-width":`calc(${e="0"===e?"0px":e} * var(--tw-divide-x-reverse))`,"border-left-width":`calc(${e} * calc(1 - var(--tw-divide-x-reverse)))`}}),"divide-y":e=>({"& > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-y-reverse":"0","border-top-width":`calc(${e="0"===e?"0px":e} * calc(1 - var(--tw-divide-y-reverse)))`,"border-bottom-width":`calc(${e} * var(--tw-divide-y-reverse))`}})},{values:r("divideWidth"),type:["line-width","length","any"]}),t({".divide-y-reverse > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-y-reverse":"1"},".divide-x-reverse > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-x-reverse":"1"}})},divideStyle:({addUtilities:e})=>{e({".divide-solid > :not([hidden]) ~ :not([hidden])":{"border-style":"solid"},".divide-dashed > :not([hidden]) ~ :not([hidden])":{"border-style":"dashed"},".divide-dotted > :not([hidden]) ~ :not([hidden])":{"border-style":"dotted"},".divide-double > :not([hidden]) ~ :not([hidden])":{"border-style":"double"},".divide-none > :not([hidden]) ~ :not([hidden])":{"border-style":"none"}})},divideColor:({matchUtilities:e,theme:t,corePlugins:r})=>{e({divide:e=>r("divideOpacity")?{"& > :not([hidden]) ~ :not([hidden])":me({color:e,property:"border-color",variable:"--tw-divide-opacity"})}:{"& > :not([hidden]) ~ :not([hidden])":{"border-color":ft(e)}}},{values:(({DEFAULT:e,...t})=>t)(Gn(t("divideColor"))),type:["color","any"]})},divideOpacity:({matchUtilities:e,theme:t})=>{e({"divide-opacity":e=>({"& > :not([hidden]) ~ :not([hidden])":{"--tw-divide-opacity":e}})},{values:t("divideOpacity")})},placeSelf:({addUtilities:e})=>{e({".place-self-auto":{"place-self":"auto"},".place-self-start":{"place-self":"start"},".place-self-end":{"place-self":"end"},".place-self-center":{"place-self":"center"},".place-self-stretch":{"place-self":"stretch"}})},alignSelf:({addUtilities:e})=>{e({".self-auto":{"align-self":"auto"},".self-start":{"align-self":"flex-start"},".self-end":{"align-self":"flex-end"},".self-center":{"align-self":"center"},".self-stretch":{"align-self":"stretch"},".self-baseline":{"align-self":"baseline"}})},justifySelf:({addUtilities:e})=>{e({".justify-self-auto":{"justify-self":"auto"},".justify-self-start":{"justify-self":"start"},".justify-self-end":{"justify-self":"end"},".justify-self-center":{"justify-self":"center"},".justify-self-stretch":{"justify-self":"stretch"}})},overflow:({addUtilities:e})=>{e({".overflow-auto":{overflow:"auto"},".overflow-hidden":{overflow:"hidden"},".overflow-clip":{overflow:"clip"},".overflow-visible":{overflow:"visible"},".overflow-scroll":{overflow:"scroll"},".overflow-x-auto":{"overflow-x":"auto"},".overflow-y-auto":{"overflow-y":"auto"},".overflow-x-hidden":{"overflow-x":"hidden"},".overflow-y-hidden":{"overflow-y":"hidden"},".overflow-x-clip":{"overflow-x":"clip"},".overflow-y-clip":{"overflow-y":"clip"},".overflow-x-visible":{"overflow-x":"visible"},".overflow-y-visible":{"overflow-y":"visible"},".overflow-x-scroll":{"overflow-x":"scroll"},".overflow-y-scroll":{"overflow-y":"scroll"}})},overscrollBehavior:({addUtilities:e})=>{e({".overscroll-auto":{"overscroll-behavior":"auto"},".overscroll-contain":{"overscroll-behavior":"contain"},".overscroll-none":{"overscroll-behavior":"none"},".overscroll-y-auto":{"overscroll-behavior-y":"auto"},".overscroll-y-contain":{"overscroll-behavior-y":"contain"},".overscroll-y-none":{"overscroll-behavior-y":"none"},".overscroll-x-auto":{"overscroll-behavior-x":"auto"},".overscroll-x-contain":{"overscroll-behavior-x":"contain"},".overscroll-x-none":{"overscroll-behavior-x":"none"}})},scrollBehavior:({addUtilities:e})=>{e({".scroll-auto":{"scroll-behavior":"auto"},".scroll-smooth":{"scroll-behavior":"smooth"}})},textOverflow:({addUtilities:e})=>{e({".truncate":{overflow:"hidden","text-overflow":"ellipsis","white-space":"nowrap"},".overflow-ellipsis":{"text-overflow":"ellipsis"},".text-ellipsis":{"text-overflow":"ellipsis"},".text-clip":{"text-overflow":"clip"}})},hyphens:({addUtilities:e})=>{e({".hyphens-none":{hyphens:"none"},".hyphens-manual":{hyphens:"manual"},".hyphens-auto":{hyphens:"auto"}})},whitespace:({addUtilities:e})=>{e({".whitespace-normal":{"white-space":"normal"},".whitespace-nowrap":{"white-space":"nowrap"},".whitespace-pre":{"white-space":"pre"},".whitespace-pre-line":{"white-space":"pre-line"},".whitespace-pre-wrap":{"white-space":"pre-wrap"},".whitespace-break-spaces":{"white-space":"break-spaces"}})},textWrap:({addUtilities:e})=>{e({".text-wrap":{"text-wrap":"wrap"},".text-nowrap":{"text-wrap":"nowrap"},".text-balance":{"text-wrap":"balance"},".text-pretty":{"text-wrap":"pretty"}})},wordBreak:({addUtilities:e})=>{e({".break-normal":{"overflow-wrap":"normal","word-break":"normal"},".break-words":{"overflow-wrap":"break-word"},".break-all":{"word-break":"break-all"},".break-keep":{"word-break":"keep-all"}})},borderRadius:In("borderRadius",[["rounded",["border-radius"]],[["rounded-s",["border-start-start-radius","border-end-start-radius"]],["rounded-e",["border-start-end-radius","border-end-end-radius"]],["rounded-t",["border-top-left-radius","border-top-right-radius"]],["rounded-r",["border-top-right-radius","border-bottom-right-radius"]],["rounded-b",["border-bottom-right-radius","border-bottom-left-radius"]],["rounded-l",["border-top-left-radius","border-bottom-left-radius"]]],[["rounded-ss",["border-start-start-radius"]],["rounded-se",["border-start-end-radius"]],["rounded-ee",["border-end-end-radius"]],["rounded-es",["border-end-start-radius"]],["rounded-tl",["border-top-left-radius"]],["rounded-tr",["border-top-right-radius"]],["rounded-br",["border-bottom-right-radius"]],["rounded-bl",["border-bottom-left-radius"]]]]),borderWidth:In("borderWidth",[["border",[["@defaults border-width",{}],"border-width"]],[["border-x",[["@defaults border-width",{}],"border-left-width","border-right-width"]],["border-y",[["@defaults border-width",{}],"border-top-width","border-bottom-width"]]],[["border-s",[["@defaults border-width",{}],"border-inline-start-width"]],["border-e",[["@defaults border-width",{}],"border-inline-end-width"]],["border-t",[["@defaults border-width",{}],"border-top-width"]],["border-r",[["@defaults border-width",{}],"border-right-width"]],["border-b",[["@defaults border-width",{}],"border-bottom-width"]],["border-l",[["@defaults border-width",{}],"border-left-width"]]]],{type:["line-width","length"]}),borderStyle:({addUtilities:e})=>{e({".border-solid":{"border-style":"solid"},".border-dashed":{"border-style":"dashed"},".border-dotted":{"border-style":"dotted"},".border-double":{"border-style":"double"},".border-hidden":{"border-style":"hidden"},".border-none":{"border-style":"none"}})},borderColor:({matchUtilities:e,theme:t,corePlugins:r})=>{e({border:e=>r("borderOpacity")?me({color:e,property:"border-color",variable:"--tw-border-opacity"}):{"border-color":ft(e)}},{values:(({DEFAULT:e,...t})=>t)(Gn(t("borderColor"))),type:["color","any"]}),e({"border-x":e=>r("borderOpacity")?me({color:e,property:["border-left-color","border-right-color"],variable:"--tw-border-opacity"}):{"border-left-color":ft(e),"border-right-color":ft(e)},"border-y":e=>r("borderOpacity")?me({color:e,property:["border-top-color","border-bottom-color"],variable:"--tw-border-opacity"}):{"border-top-color":ft(e),"border-bottom-color":ft(e)}},{values:(({DEFAULT:e,...t})=>t)(Gn(t("borderColor"))),type:["color","any"]}),e({"border-s":e=>r("borderOpacity")?me({color:e,property:"border-inline-start-color",variable:"--tw-border-opacity"}):{"border-inline-start-color":ft(e)},"border-e":e=>r("borderOpacity")?me({color:e,property:"border-inline-end-color",variable:"--tw-border-opacity"}):{"border-inline-end-color":ft(e)},"border-t":e=>r("borderOpacity")?me({color:e,property:"border-top-color",variable:"--tw-border-opacity"}):{"border-top-color":ft(e)},"border-r":e=>r("borderOpacity")?me({color:e,property:"border-right-color",variable:"--tw-border-opacity"}):{"border-right-color":ft(e)},"border-b":e=>r("borderOpacity")?me({color:e,property:"border-bottom-color",variable:"--tw-border-opacity"}):{"border-bottom-color":ft(e)},"border-l":e=>r("borderOpacity")?me({color:e,property:"border-left-color",variable:"--tw-border-opacity"}):{"border-left-color":ft(e)}},{values:(({DEFAULT:e,...t})=>t)(Gn(t("borderColor"))),type:["color","any"]})},borderOpacity:In("borderOpacity",[["border-opacity",["--tw-border-opacity"]]]),backgroundColor:({matchUtilities:e,theme:t,corePlugins:r})=>{e({bg:e=>r("backgroundOpacity")?me({color:e,property:"background-color",variable:"--tw-bg-opacity"}):{"background-color":ft(e)}},{values:Gn(t("backgroundColor")),type:["color","any"]})},backgroundOpacity:In("backgroundOpacity",[["bg-opacity",["--tw-bg-opacity"]]]),backgroundImage:In("backgroundImage",[["bg",["background-image"]]],{type:["lookup","image","url"]}),gradientColorStops:(()=>{function e(e){return he(e,0,"rgb(255 255 255 / 0)")}return function({matchUtilities:t,theme:r,addDefaults:n}){n("gradient-color-stops",{"--tw-gradient-from-position":" ","--tw-gradient-via-position":" ","--tw-gradient-to-position":" "});let i={values:Gn(r("gradientColorStops")),type:["color","any"]},o={values:r("gradientColorStopPositions"),type:["length","percentage"]};t({from:t=>{let r=e(t);return{"@defaults gradient-color-stops":{},"--tw-gradient-from":`${ft(t)} var(--tw-gradient-from-position)`,"--tw-gradient-to":`${r} var(--tw-gradient-to-position)`,"--tw-gradient-stops":"var(--tw-gradient-from), var(--tw-gradient-to)"}}},i),t({from:e=>({"--tw-gradient-from-position":e})},o),t({via:t=>({"@defaults gradient-color-stops":{},"--tw-gradient-to":`${e(t)} var(--tw-gradient-to-position)`,"--tw-gradient-stops":`var(--tw-gradient-from), ${ft(t)} var(--tw-gradient-via-position), var(--tw-gradient-to)`})},i),t({via:e=>({"--tw-gradient-via-position":e})},o),t({to:e=>({"@defaults gradient-color-stops":{},"--tw-gradient-to":`${ft(e)} var(--tw-gradient-to-position)`})},i),t({to:e=>({"--tw-gradient-to-position":e})},o)}})(),boxDecorationBreak:({addUtilities:e})=>{e({".decoration-slice":{"box-decoration-break":"slice"},".decoration-clone":{"box-decoration-break":"clone"},".box-decoration-slice":{"box-decoration-break":"slice"},".box-decoration-clone":{"box-decoration-break":"clone"}})},backgroundSize:In("backgroundSize",[["bg",["background-size"]]],{type:["lookup","length","percentage","size"]}),backgroundAttachment:({addUtilities:e})=>{e({".bg-fixed":{"background-attachment":"fixed"},".bg-local":{"background-attachment":"local"},".bg-scroll":{"background-attachment":"scroll"}})},backgroundClip:({addUtilities:e})=>{e({".bg-clip-border":{"background-clip":"border-box"},".bg-clip-padding":{"background-clip":"padding-box"},".bg-clip-content":{"background-clip":"content-box"},".bg-clip-text":{"background-clip":"text"}})},backgroundPosition:In("backgroundPosition",[["bg",["background-position"]]],{type:["lookup",["position",{preferOnConflict:!0}]]}),backgroundRepeat:({addUtilities:e})=>{e({".bg-repeat":{"background-repeat":"repeat"},".bg-no-repeat":{"background-repeat":"no-repeat"},".bg-repeat-x":{"background-repeat":"repeat-x"},".bg-repeat-y":{"background-repeat":"repeat-y"},".bg-repeat-round":{"background-repeat":"round"},".bg-repeat-space":{"background-repeat":"space"}})},backgroundOrigin:({addUtilities:e})=>{e({".bg-origin-border":{"background-origin":"border-box"},".bg-origin-padding":{"background-origin":"padding-box"},".bg-origin-content":{"background-origin":"content-box"}})},fill:({matchUtilities:e,theme:t})=>{e({fill:e=>({fill:ft(e)})},{values:Gn(t("fill")),type:["color","any"]})},stroke:({matchUtilities:e,theme:t})=>{e({stroke:e=>({stroke:ft(e)})},{values:Gn(t("stroke")),type:["color","url","any"]})},strokeWidth:In("strokeWidth",[["stroke",["stroke-width"]]],{type:["length","number","percentage"]}),objectFit:({addUtilities:e})=>{e({".object-contain":{"object-fit":"contain"},".object-cover":{"object-fit":"cover"},".object-fill":{"object-fit":"fill"},".object-none":{"object-fit":"none"},".object-scale-down":{"object-fit":"scale-down"}})},objectPosition:In("objectPosition",[["object",["object-position"]]]),padding:In("padding",[["p",["padding"]],[["px",["padding-left","padding-right"]],["py",["padding-top","padding-bottom"]]],[["ps",["padding-inline-start"]],["pe",["padding-inline-end"]],["pt",["padding-top"]],["pr",["padding-right"]],["pb",["padding-bottom"]],["pl",["padding-left"]]]]),textAlign:({addUtilities:e})=>{e({".text-left":{"text-align":"left"},".text-center":{"text-align":"center"},".text-right":{"text-align":"right"},".text-justify":{"text-align":"justify"},".text-start":{"text-align":"start"},".text-end":{"text-align":"end"}})},textIndent:In("textIndent",[["indent",["text-indent"]]],{supportsNegativeValues:!0}),verticalAlign:({addUtilities:e,matchUtilities:t})=>{e({".align-baseline":{"vertical-align":"baseline"},".align-top":{"vertical-align":"top"},".align-middle":{"vertical-align":"middle"},".align-bottom":{"vertical-align":"bottom"},".align-text-top":{"vertical-align":"text-top"},".align-text-bottom":{"vertical-align":"text-bottom"},".align-sub":{"vertical-align":"sub"},".align-super":{"vertical-align":"super"}}),t({align:e=>({"vertical-align":e})})},fontFamily:({matchUtilities:e,theme:t})=>{e({font:e=>{let[t,r={}]=Array.isArray(e)&&Q(e[1])?e:[e],{fontFeatureSettings:n,fontVariationSettings:i}=r;return{"font-family":Array.isArray(t)?t.join(", "):t,...void 0===n?{}:{"font-feature-settings":n},...void 0===i?{}:{"font-variation-settings":i}}}},{values:t("fontFamily"),type:["lookup","generic-name","family-name"]})},fontSize:({matchUtilities:e,theme:t})=>{e({text:(e,{modifier:t})=>{let[r,n]=Array.isArray(e)?e:[e];if(t)return{"font-size":r,"line-height":t};let{lineHeight:i,letterSpacing:o,fontWeight:s}=Q(n)?n:{lineHeight:n};return{"font-size":r,...void 0===i?{}:{"line-height":i},...void 0===o?{}:{"letter-spacing":o},...void 0===s?{}:{"font-weight":s}}}},{values:t("fontSize"),modifiers:t("lineHeight"),type:["absolute-size","relative-size","length","percentage"]})},fontWeight:In("fontWeight",[["font",["fontWeight"]]],{type:["lookup","number","any"]}),textTransform:({addUtilities:e})=>{e({".uppercase":{"text-transform":"uppercase"},".lowercase":{"text-transform":"lowercase"},".capitalize":{"text-transform":"capitalize"},".normal-case":{"text-transform":"none"}})},fontStyle:({addUtilities:e})=>{e({".italic":{"font-style":"italic"},".not-italic":{"font-style":"normal"}})},fontVariantNumeric:({addDefaults:e,addUtilities:t})=>{let r="var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)";e("font-variant-numeric",{"--tw-ordinal":" ","--tw-slashed-zero":" ","--tw-numeric-figure":" ","--tw-numeric-spacing":" ","--tw-numeric-fraction":" "}),t({".normal-nums":{"font-variant-numeric":"normal"},".ordinal":{"@defaults font-variant-numeric":{},"--tw-ordinal":"ordinal","font-variant-numeric":r},".slashed-zero":{"@defaults font-variant-numeric":{},"--tw-slashed-zero":"slashed-zero","font-variant-numeric":r},".lining-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-figure":"lining-nums","font-variant-numeric":r},".oldstyle-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-figure":"oldstyle-nums","font-variant-numeric":r},".proportional-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-spacing":"proportional-nums","font-variant-numeric":r},".tabular-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-spacing":"tabular-nums","font-variant-numeric":r},".diagonal-fractions":{"@defaults font-variant-numeric":{},"--tw-numeric-fraction":"diagonal-fractions","font-variant-numeric":r},".stacked-fractions":{"@defaults font-variant-numeric":{},"--tw-numeric-fraction":"stacked-fractions","font-variant-numeric":r}})},lineHeight:In("lineHeight",[["leading",["lineHeight"]]]),letterSpacing:In("letterSpacing",[["tracking",["letterSpacing"]]],{supportsNegativeValues:!0}),textColor:({matchUtilities:e,theme:t,corePlugins:r})=>{e({text:e=>r("textOpacity")?me({color:e,property:"color",variable:"--tw-text-opacity"}):{color:ft(e)}},{values:Gn(t("textColor")),type:["color","any"]})},textOpacity:In("textOpacity",[["text-opacity",["--tw-text-opacity"]]]),textDecoration:({addUtilities:e})=>{e({".underline":{"text-decoration-line":"underline"},".overline":{"text-decoration-line":"overline"},".line-through":{"text-decoration-line":"line-through"},".no-underline":{"text-decoration-line":"none"}})},textDecorationColor:({matchUtilities:e,theme:t})=>{e({decoration:e=>({"text-decoration-color":ft(e)})},{values:Gn(t("textDecorationColor")),type:["color","any"]})},textDecorationStyle:({addUtilities:e})=>{e({".decoration-solid":{"text-decoration-style":"solid"},".decoration-double":{"text-decoration-style":"double"},".decoration-dotted":{"text-decoration-style":"dotted"},".decoration-dashed":{"text-decoration-style":"dashed"},".decoration-wavy":{"text-decoration-style":"wavy"}})},textDecorationThickness:In("textDecorationThickness",[["decoration",["text-decoration-thickness"]]],{type:["length","percentage"]}),textUnderlineOffset:In("textUnderlineOffset",[["underline-offset",["text-underline-offset"]]],{type:["length","percentage","any"]}),fontSmoothing:({addUtilities:e})=>{e({".antialiased":{"-webkit-font-smoothing":"antialiased","-moz-osx-font-smoothing":"grayscale"},".subpixel-antialiased":{"-webkit-font-smoothing":"auto","-moz-osx-font-smoothing":"auto"}})},placeholderColor:({matchUtilities:e,theme:t,corePlugins:r})=>{e({placeholder:e=>r("placeholderOpacity")?{"&::placeholder":me({color:e,property:"color",variable:"--tw-placeholder-opacity"})}:{"&::placeholder":{color:ft(e)}}},{values:Gn(t("placeholderColor")),type:["color","any"]})},placeholderOpacity:({matchUtilities:e,theme:t})=>{e({"placeholder-opacity":e=>({"&::placeholder":{"--tw-placeholder-opacity":e}})},{values:t("placeholderOpacity")})},caretColor:({matchUtilities:e,theme:t})=>{e({caret:e=>({"caret-color":ft(e)})},{values:Gn(t("caretColor")),type:["color","any"]})},accentColor:({matchUtilities:e,theme:t})=>{e({accent:e=>({"accent-color":ft(e)})},{values:Gn(t("accentColor")),type:["color","any"]})},opacity:In("opacity",[["opacity",["opacity"]]]),backgroundBlendMode:({addUtilities:e})=>{e({".bg-blend-normal":{"background-blend-mode":"normal"},".bg-blend-multiply":{"background-blend-mode":"multiply"},".bg-blend-screen":{"background-blend-mode":"screen"},".bg-blend-overlay":{"background-blend-mode":"overlay"},".bg-blend-darken":{"background-blend-mode":"darken"},".bg-blend-lighten":{"background-blend-mode":"lighten"},".bg-blend-color-dodge":{"background-blend-mode":"color-dodge"},".bg-blend-color-burn":{"background-blend-mode":"color-burn"},".bg-blend-hard-light":{"background-blend-mode":"hard-light"},".bg-blend-soft-light":{"background-blend-mode":"soft-light"},".bg-blend-difference":{"background-blend-mode":"difference"},".bg-blend-exclusion":{"background-blend-mode":"exclusion"},".bg-blend-hue":{"background-blend-mode":"hue"},".bg-blend-saturation":{"background-blend-mode":"saturation"},".bg-blend-color":{"background-blend-mode":"color"},".bg-blend-luminosity":{"background-blend-mode":"luminosity"}})},mixBlendMode:({addUtilities:e})=>{e({".mix-blend-normal":{"mix-blend-mode":"normal"},".mix-blend-multiply":{"mix-blend-mode":"multiply"},".mix-blend-screen":{"mix-blend-mode":"screen"},".mix-blend-overlay":{"mix-blend-mode":"overlay"},".mix-blend-darken":{"mix-blend-mode":"darken"},".mix-blend-lighten":{"mix-blend-mode":"lighten"},".mix-blend-color-dodge":{"mix-blend-mode":"color-dodge"},".mix-blend-color-burn":{"mix-blend-mode":"color-burn"},".mix-blend-hard-light":{"mix-blend-mode":"hard-light"},".mix-blend-soft-light":{"mix-blend-mode":"soft-light"},".mix-blend-difference":{"mix-blend-mode":"difference"},".mix-blend-exclusion":{"mix-blend-mode":"exclusion"},".mix-blend-hue":{"mix-blend-mode":"hue"},".mix-blend-saturation":{"mix-blend-mode":"saturation"},".mix-blend-color":{"mix-blend-mode":"color"},".mix-blend-luminosity":{"mix-blend-mode":"luminosity"},".mix-blend-plus-darker":{"mix-blend-mode":"plus-darker"},".mix-blend-plus-lighter":{"mix-blend-mode":"plus-lighter"}})},boxShadow:(()=>{let e=sn("boxShadow"),t=["var(--tw-ring-offset-shadow, 0 0 #0000)","var(--tw-ring-shadow, 0 0 #0000)","var(--tw-shadow)"].join(", ");return function({matchUtilities:r,addDefaults:n,theme:i}){n("box-shadow",{"--tw-ring-offset-shadow":"0 0 #0000","--tw-ring-shadow":"0 0 #0000","--tw-shadow":"0 0 #0000","--tw-shadow-colored":"0 0 #0000"}),r({shadow:r=>{let n=ve(r=e(r));for(let e of n)!e.valid||(e.color="var(--tw-shadow-color)");return{"@defaults box-shadow":{},"--tw-shadow":"none"===r?"0 0 #0000":r,"--tw-shadow-colored":"none"===r?"0 0 #0000":we(n),"box-shadow":t}}},{values:i("boxShadow"),type:["shadow"]})}})(),boxShadowColor:({matchUtilities:e,theme:t})=>{e({shadow:e=>({"--tw-shadow-color":ft(e),"--tw-shadow":"var(--tw-shadow-colored)"})},{values:Gn(t("boxShadowColor")),type:["color","any"]})},outlineStyle:({addUtilities:e})=>{e({".outline-none":{outline:"2px solid transparent","outline-offset":"2px"},".outline":{"outline-style":"solid"},".outline-dashed":{"outline-style":"dashed"},".outline-dotted":{"outline-style":"dotted"},".outline-double":{"outline-style":"double"}})},outlineWidth:In("outlineWidth",[["outline",["outline-width"]]],{type:["length","number","percentage"]}),outlineOffset:In("outlineOffset",[["outline-offset",["outline-offset"]]],{type:["length","number","percentage","any"],supportsNegativeValues:!0}),outlineColor:({matchUtilities:e,theme:t})=>{e({outline:e=>({"outline-color":ft(e)})},{values:Gn(t("outlineColor")),type:["color","any"]})},ringWidth:({matchUtilities:e,addDefaults:t,addUtilities:r,theme:n,config:i})=>{let o=(()=>{if(V(i(),"respectDefaultRingColorOpacity"))return n("ringColor.DEFAULT");let e=n("ringOpacity.DEFAULT","0.5");return n("ringColor")?.DEFAULT?he(n("ringColor")?.DEFAULT,e,`rgb(147 197 253 / ${e})`):`rgb(147 197 253 / ${e})`})();t("ring-width",{"--tw-ring-inset":" ","--tw-ring-offset-width":n("ringOffsetWidth.DEFAULT","0px"),"--tw-ring-offset-color":n("ringOffsetColor.DEFAULT","#fff"),"--tw-ring-color":o,"--tw-ring-offset-shadow":"0 0 #0000","--tw-ring-shadow":"0 0 #0000","--tw-shadow":"0 0 #0000","--tw-shadow-colored":"0 0 #0000"}),e({ring:e=>({"@defaults ring-width":{},"--tw-ring-offset-shadow":"var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)","--tw-ring-shadow":`var(--tw-ring-inset) 0 0 0 calc(${e} + var(--tw-ring-offset-width)) var(--tw-ring-color)`,"box-shadow":["var(--tw-ring-offset-shadow)","var(--tw-ring-shadow)","var(--tw-shadow, 0 0 #0000)"].join(", ")})},{values:n("ringWidth"),type:"length"}),r({".ring-inset":{"@defaults ring-width":{},"--tw-ring-inset":"inset"}})},ringColor:({matchUtilities:e,theme:t,corePlugins:r})=>{e({ring:e=>r("ringOpacity")?me({color:e,property:"--tw-ring-color",variable:"--tw-ring-opacity"}):{"--tw-ring-color":ft(e)}},{values:Object.fromEntries(Object.entries(Gn(t("ringColor"))).filter((([e])=>"DEFAULT"!==e))),type:["color","any"]})},ringOpacity:e=>{let{config:t}=e;return In("ringOpacity",[["ring-opacity",["--tw-ring-opacity"]]],{filterDefault:!V(t(),"respectDefaultRingColorOpacity")})(e)},ringOffsetWidth:In("ringOffsetWidth",[["ring-offset",["--tw-ring-offset-width"]]],{type:"length"}),ringOffsetColor:({matchUtilities:e,theme:t})=>{e({"ring-offset":e=>({"--tw-ring-offset-color":ft(e)})},{values:Gn(t("ringOffsetColor")),type:["color","any"]})},blur:({matchUtilities:e,theme:t})=>{e({blur:e=>({"--tw-blur":""===e.trim()?" ":`blur(${e})`,"@defaults filter":{},filter:si})},{values:t("blur")})},brightness:({matchUtilities:e,theme:t})=>{e({brightness:e=>({"--tw-brightness":`brightness(${e})`,"@defaults filter":{},filter:si})},{values:t("brightness")})},contrast:({matchUtilities:e,theme:t})=>{e({contrast:e=>({"--tw-contrast":`contrast(${e})`,"@defaults filter":{},filter:si})},{values:t("contrast")})},dropShadow:({matchUtilities:e,theme:t})=>{e({"drop-shadow":e=>({"--tw-drop-shadow":Array.isArray(e)?e.map((e=>`drop-shadow(${e})`)).join(" "):`drop-shadow(${e})`,"@defaults filter":{},filter:si})},{values:t("dropShadow")})},grayscale:({matchUtilities:e,theme:t})=>{e({grayscale:e=>({"--tw-grayscale":`grayscale(${e})`,"@defaults filter":{},filter:si})},{values:t("grayscale")})},hueRotate:({matchUtilities:e,theme:t})=>{e({"hue-rotate":e=>({"--tw-hue-rotate":`hue-rotate(${e})`,"@defaults filter":{},filter:si})},{values:t("hueRotate"),supportsNegativeValues:!0})},invert:({matchUtilities:e,theme:t})=>{e({invert:e=>({"--tw-invert":`invert(${e})`,"@defaults filter":{},filter:si})},{values:t("invert")})},saturate:({matchUtilities:e,theme:t})=>{e({saturate:e=>({"--tw-saturate":`saturate(${e})`,"@defaults filter":{},filter:si})},{values:t("saturate")})},sepia:({matchUtilities:e,theme:t})=>{e({sepia:e=>({"--tw-sepia":`sepia(${e})`,"@defaults filter":{},filter:si})},{values:t("sepia")})},filter:({addDefaults:e,addUtilities:t})=>{e("filter",{"--tw-blur":" ","--tw-brightness":" ","--tw-contrast":" ","--tw-grayscale":" ","--tw-hue-rotate":" ","--tw-invert":" ","--tw-saturate":" ","--tw-sepia":" ","--tw-drop-shadow":" "}),t({".filter":{"@defaults filter":{},filter:si},".filter-none":{filter:"none"}})},backdropBlur:({matchUtilities:e,theme:t})=>{e({"backdrop-blur":e=>({"--tw-backdrop-blur":""===e.trim()?" ":`blur(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropBlur")})},backdropBrightness:({matchUtilities:e,theme:t})=>{e({"backdrop-brightness":e=>({"--tw-backdrop-brightness":`brightness(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropBrightness")})},backdropContrast:({matchUtilities:e,theme:t})=>{e({"backdrop-contrast":e=>({"--tw-backdrop-contrast":`contrast(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropContrast")})},backdropGrayscale:({matchUtilities:e,theme:t})=>{e({"backdrop-grayscale":e=>({"--tw-backdrop-grayscale":`grayscale(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropGrayscale")})},backdropHueRotate:({matchUtilities:e,theme:t})=>{e({"backdrop-hue-rotate":e=>({"--tw-backdrop-hue-rotate":`hue-rotate(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropHueRotate"),supportsNegativeValues:!0})},backdropInvert:({matchUtilities:e,theme:t})=>{e({"backdrop-invert":e=>({"--tw-backdrop-invert":`invert(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropInvert")})},backdropOpacity:({matchUtilities:e,theme:t})=>{e({"backdrop-opacity":e=>({"--tw-backdrop-opacity":`opacity(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropOpacity")})},backdropSaturate:({matchUtilities:e,theme:t})=>{e({"backdrop-saturate":e=>({"--tw-backdrop-saturate":`saturate(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropSaturate")})},backdropSepia:({matchUtilities:e,theme:t})=>{e({"backdrop-sepia":e=>({"--tw-backdrop-sepia":`sepia(${e})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai})},{values:t("backdropSepia")})},backdropFilter:({addDefaults:e,addUtilities:t})=>{e("backdrop-filter",{"--tw-backdrop-blur":" ","--tw-backdrop-brightness":" ","--tw-backdrop-contrast":" ","--tw-backdrop-grayscale":" ","--tw-backdrop-hue-rotate":" ","--tw-backdrop-invert":" ","--tw-backdrop-opacity":" ","--tw-backdrop-saturate":" ","--tw-backdrop-sepia":" "}),t({".backdrop-filter":{"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ai,"backdrop-filter":ai},".backdrop-filter-none":{"-webkit-backdrop-filter":"none","backdrop-filter":"none"}})},transitionProperty:({matchUtilities:e,theme:t})=>{let r=t("transitionTimingFunction.DEFAULT"),n=t("transitionDuration.DEFAULT");e({transition:e=>({"transition-property":e,..."none"===e?{}:{"transition-timing-function":r,"transition-duration":n}})},{values:t("transitionProperty")})},transitionDelay:In("transitionDelay",[["delay",["transitionDelay"]]]),transitionDuration:In("transitionDuration",[["duration",["transitionDuration"]]],{filterDefault:!0}),transitionTimingFunction:In("transitionTimingFunction",[["ease",["transitionTimingFunction"]]],{filterDefault:!0}),willChange:In("willChange",[["will-change",["will-change"]]]),contain:({addDefaults:e,addUtilities:t})=>{let r="var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style)";e("contain",{"--tw-contain-size":" ","--tw-contain-layout":" ","--tw-contain-paint":" ","--tw-contain-style":" "}),t({".contain-none":{contain:"none"},".contain-content":{contain:"content"},".contain-strict":{contain:"strict"},".contain-size":{"@defaults contain":{},"--tw-contain-size":"size",contain:r},".contain-inline-size":{"@defaults contain":{},"--tw-contain-size":"inline-size",contain:r},".contain-layout":{"@defaults contain":{},"--tw-contain-layout":"layout",contain:r},".contain-paint":{"@defaults contain":{},"--tw-contain-paint":"paint",contain:r},".contain-style":{"@defaults contain":{},"--tw-contain-style":"style",contain:r}})},content:In("content",[["content",["--tw-content",["content","var(--tw-content)"]]]]),forcedColorAdjust:({addUtilities:e})=>{e({".forced-color-adjust-auto":{"forced-color-adjust":"auto"},".forced-color-adjust-none":{"forced-color-adjust":"none"}})}}}));function di(e){if(void 0===e)return!1;if("true"===e||"1"===e)return!0;if("false"===e||"0"===e)return!1;if("*"===e)return!0;let t=e.split(",").map((e=>e.split(":")[0]));return!t.includes("-tailwindcss")&&!!t.includes("tailwindcss")}var pi,fi,hi,mi,gi,yi,bi,vi=p((()=>{g(),pi=void 0!==e?{NODE_ENV:"production",DEBUG:di(e.env.DEBUG)}:{NODE_ENV:"production",DEBUG:!1},fi=new Map,hi=new Map,mi=new Map,gi=new Map,yi=new String("*"),bi=Symbol("__NONE__")}));function wi(e){let t=[],r=!1;for(let n=0;n0)}var xi,ki,Si,Ci=p((()=>{g(),xi=new Map([["{","}"],["[","]"],["(",")"]]),ki=new Map(Array.from(xi.entries()).map((([e,t])=>[t,e]))),Si=new Set(['"',"'","`"])}));function Ai(e){let[t]=Oi(e);return t.forEach((([e,t])=>e.removeChild(t))),e.nodes.push(...t.map((([,e])=>e))),e}function Oi(e){let t=[],r=null;for(let n of e.nodes)if("combinator"===n.type)t=t.filter((([,e])=>Pi(e).includes("jumpable"))),r=null;else if("pseudo"===n.type){_i(n)?(r=n,t.push([e,n,null])):r&&Ti(n,r)?t.push([e,n,r]):r=null;for(let e of n.nodes??[]){let[n,i]=Oi(e);r=i||r,t.push(...n)}}return[t,r]}function Ei(e){return e.value.startsWith("::")||void 0!==ji[e.value]}function _i(e){return Ei(e)&&Pi(e).includes("terminal")}function Ti(e,t){return"pseudo"===e.type&&!Ei(e)&&Pi(t).includes("actionable")}function Pi(e){return ji[e.value]??ji.__default__}var ji,Ii=p((()=>{g(),ji={"::after":["terminal","jumpable"],"::backdrop":["terminal","jumpable"],"::before":["terminal","jumpable"],"::cue":["terminal"],"::cue-region":["terminal"],"::first-letter":["terminal","jumpable"],"::first-line":["terminal","jumpable"],"::grammar-error":["terminal"],"::marker":["terminal","jumpable"],"::part":["terminal","actionable"],"::placeholder":["terminal","jumpable"],"::selection":["terminal","jumpable"],"::slotted":["terminal"],"::spelling-error":["terminal"],"::target-text":["terminal"],"::file-selector-button":["terminal","actionable"],"::deep":["actionable"],"::v-deep":["actionable"],"::ng-deep":["actionable"],":after":["terminal","jumpable"],":before":["terminal","jumpable"],":first-letter":["terminal","jumpable"],":first-line":["terminal","jumpable"],":where":[],":is":[],":has":[],__default__:["terminal","actionable"]}}));function Di(e,{context:t,candidate:r}){let n=t?.tailwindConfig.prefix??"",i=e.map((e=>{let t=(0,Ui.default)().astSync(e.format);return{...e,ast:e.respectPrefix?kn(n,t):t}})),o=Ui.default.root({nodes:[Ui.default.selector({nodes:[Ui.default.className({value:An(r)})]})]});for(let{ast:e}of i)[o,e]=Mi(o,e),e.walkNesting((e=>e.replaceWith(...o.nodes[0].nodes))),o=e;return o}function $i(e){let t=[];for(;e.prev()&&"combinator"!==e.prev().type;)e=e.prev();for(;e&&"combinator"!==e.type;)t.push(e),e=e.next();return t}function Bi(e,t){let r=!1;e.walk((e=>{if("class"===e.type&&e.value===t)return r=!0,!1})),r||e.remove()}function Ri(e,t,{context:r,candidate:n,base:i}){i=i??ye(n,r?.tailwindConfig?.separator??":").pop();let o=(0,Ui.default)().astSync(e);if(o.walkClasses((e=>{e.raws&&e.value.includes(i)&&(e.raws.value=An((0,zi.default)(e.raws.value)))})),o.each((e=>Bi(e,i))),0===o.length)return null;let s=Array.isArray(t)?Di(t,{context:r,candidate:n}):t;if(null===s)return o.toString();let a=Ui.default.comment({value:"/*__simple__*/"}),l=Ui.default.comment({value:"/*__simple__*/"});return o.walkClasses((e=>{if(e.value!==i)return;let t=e.parent,r=s.nodes[0].nodes;if(1===t.nodes.length)return void e.replaceWith(...r);let n=$i(e);t.insertBefore(n[0],a),t.insertAfter(n[n.length-1],l);for(let e of r)t.insertBefore(n[0],e.clone());e.remove(),n=$i(a);let o=t.index(a);t.nodes.splice(o,n.length,...function(e){return e.sort(((t,r)=>"tag"===t.type&&"class"===r.type?-1:"class"===t.type&&"tag"===r.type?1:"class"===t.type&&"pseudo"===r.type&&r.value.startsWith("::")?-1:"pseudo"===t.type&&t.value.startsWith("::")&&"class"===r.type?1:e.index(t)-e.index(r))),e}(Ui.default.selector({nodes:n})).nodes),a.remove(),l.remove()})),o.walkPseudos((e=>{e.value===Li&&e.replaceWith(e.nodes)})),o.each((e=>Ai(e))),o.toString()}function Mi(e,t){let r=[];return e.walkPseudos((e=>{e.value===Li&&r.push({pseudo:e,value:e.nodes[0].toString()})})),t.walkPseudos((e=>{if(e.value!==Li)return;let t=e.nodes[0].toString(),n=r.find((e=>e.value===t));if(!n)return;let i=[],o=e.next();for(;o&&"combinator"!==o.type;)i.push(o),o=o.next();let s=o;n.pseudo.parent.insertAfter(n.pseudo,Ui.default.selector({nodes:i.map((e=>e.clone()))})),e.remove(),i.forEach((e=>e.remove())),s&&"combinator"===s.type&&s.remove()})),[e,t]}var Ui,zi,Li,Fi=p((()=>{g(),Ui=m(on()),zi=m(Cr()),En(),Cn(),Ii(),be(),Li=":merge"}));function Ni(e,t){let r=(0,Vi.default)().astSync(e);return r.each((e=>{"pseudo"===e.nodes[0].type&&":is"===e.nodes[0].value&&e.nodes.every((e=>"combinator"!==e.type))||(e.nodes=[Vi.default.pseudo({value:":is",nodes:[e.clone()]})]),Ai(e)})),`${t} ${r.toString()}`}var Vi,Wi=p((()=>{g(),Vi=m(on()),Ii()}));function qi(e){return oo.transformSync(e)}function Gi(e,t){if(0===e.length||""===t.tailwindConfig.prefix)return e;for(let r of e){let[e]=r;if(e.options.respectPrefix){let e=Wt.root({nodes:[r[1].clone()]}),n=r[1].raws.tailwind.classCandidate;e.walkRules((e=>{let r=n.startsWith("-");e.selector=kn(t.tailwindConfig.prefix,e.selector,r)})),r[1]=e.nodes[0]}}return e}function Yi(e,t){if(0===e.length)return e;let r=[];function n(e){return e.parent&&"atrule"===e.parent.type&&"keyframes"===e.parent.name}for(let[i,o]of e){let e=Wt.root({nodes:[o.clone()]});e.walkRules((e=>{if(n(e))return;let r=(0,io.default)().astSync(e.selector);r.each((e=>Bi(e,t))),Ke(r,(e=>e===t?`!${e}`:e)),e.selector=r.toString(),e.walkDecls((e=>e.important=!0))})),r.push([{...i,important:!0},e.nodes[0]])}return r}function Hi(e,t,r){if(0===t.length)return t;let n={modifier:null,value:bi};{let[t,...i]=ye(e,"/");if(i.length>1&&(t=t+"/"+i.slice(0,-1).join("/"),i=i.slice(-1)),i.length&&!r.variantMap.has(e)&&(e=t,n.modifier=i[0],!V(r.tailwindConfig,"generalizedModifiers")))return[]}if(e.endsWith("]")&&!e.startsWith("[")){let t=/(.)(-?)\[(.*)\]/g.exec(e);if(t){let[,r,i,o]=t;if("@"===r&&"-"===i)return[];if("@"!==r&&""===i)return[];e=e.replace(`${i}[${o}]`,""),n.value=o}}if(no(e)&&!r.variantMap.has(e)){let t=r.offsets.recordVariant(e),n=ye(Oe(e.slice(1,-1)),",");if(n.length>1)return[];if(!n.every(Ao))return[];let i=n.map(((e,n)=>[r.offsets.applyParallelOffset(t,n),Oo(e.trim())]));r.variantMap.set(e,i)}if(r.variantMap.has(e)){let i=no(e),o=r.variantOptions.get(e)?.[Bo]??{},s=r.variantMap.get(e).slice(),a=[],l=!(i||!1===o.respectPrefix);for(let[i,o]of t){if("user"===i.layer)continue;let t=Wt.root({nodes:[o.clone()]});for(let[o,c,u]of s){let d=function(){f.raws.neededBackup||(f.raws.neededBackup=!0,f.walkRules((e=>e.raws.originalSelector=e.selector)))},p=function(e){return d(),f.each((t=>{"rule"===t.type&&(t.selectors=t.selectors.map((t=>e({get className(){return qi(t)},selector:t}))))})),f},f=(u??t).clone(),h=[],m=c({get container(){return d(),f},separator:r.tailwindConfig.separator,modifySelectors:p,wrap(e){let t=f.nodes;f.removeAll(),e.append(t),f.append(e)},format(e){h.push({format:e,respectPrefix:l})},args:n});if(Array.isArray(m)){for(let[e,t]of m.entries())s.push([r.offsets.applyParallelOffset(o,e),t,f.clone()]);continue}if("string"==typeof m&&h.push({format:m,respectPrefix:l}),null===m)continue;f.raws.neededBackup&&(delete f.raws.neededBackup,f.walkRules((t=>{let n=t.raws.originalSelector;if(!n||(delete t.raws.originalSelector,n===t.selector))return;let i=t.selector,o=(0,io.default)((t=>{t.walkClasses((t=>{t.value=`${e}${r.tailwindConfig.separator}${t.value}`}))})).processSync(n);h.push({format:i.replace(o,"&"),respectPrefix:l}),t.selector=n}))),f.nodes[0].raws.tailwind={...f.nodes[0].raws.tailwind,parentLayer:i.layer};let g=[{...i,sort:r.offsets.applyVariantOffset(i.sort,o,Object.assign(n,r.variantOptions.get(e))),collectedFormats:(i.collectedFormats??[]).concat(h)},f.nodes[0]];a.push(g)}}return a}return[]}function Qi(e,t,r={}){return Q(e)||Array.isArray(e)?Array.isArray(e)?Qi(e[0],t,e[1]):(t.has(e)||t.set(e,vn(e)),[t.get(e),r]):[[e],r]}function Ji(e){let t=!0;return e.walkDecls((e=>{if(!Xi(e.prop,e.value))return t=!1,!1})),t}function Xi(e,t){if(function(e){if(!e.includes("://"))return!1;try{let t=new URL(e);return""!==t.scheme&&""!==t.host}catch(e){return!1}}(`${e}:${t}`))return!1;try{return Wt.parse(`a{${e}:${t}}`).toResult(),!0}catch(e){return!1}}function*Ki(e,t){for(let r of e)r[1].raws.tailwind={...r[1].raws.tailwind,classCandidate:t,preserveSource:r[0].options?.preserveSource??!1},yield r}function*Zi(e,t){let r=t.tailwindConfig.separator,[n,...i]=function(e,t){return e===yi?[yi]:ye(e,t)}(e,r).reverse(),o=!1;n.startsWith("!")&&(o=!0,n=n.slice(1));for(let r of function*(e,t){t.candidateRuleMap.has(e)&&(yield[t.candidateRuleMap.get(e),"DEFAULT"]),yield*function*(e){null!==e&&(yield[e,"DEFAULT"])}(function(e,t){let[,r,n]=e.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/)??[];if(void 0===n||!function(e){return so.test(e)}(r)||!wi(n))return null;let i=Oe(n,{property:r});return Xi(r,i)?[[{sort:t.offsets.arbitraryProperty(e),layer:"utilities",options:{respectImportant:!0}},()=>({[_n(e)]:{[r]:i}})]]:null}(e,t));let r=e,n=!1,i=t.tailwindConfig.prefix,o=i.length,s=r.startsWith(i)||r.startsWith(`-${i}`);"-"===r[o]&&s&&(n=!0,r=i+r.slice(o+1)),n&&t.candidateRuleMap.has(r)&&(yield[t.candidateRuleMap.get(r),"-DEFAULT"]);for(let[e,i]of function*(e){let t=1/0;for(;t>=0;){let r,n=!1;if(t===1/0&&e.endsWith("]")){let t=e.indexOf("[");"-"===e[t-1]?r=t-1:"/"===e[t-1]?(r=t-1,n=!0):r=-1}else t===1/0&&e.includes("/")?(r=e.lastIndexOf("/"),n=!0):r=e.lastIndexOf("-",t);if(r<0)break;let i=e.slice(0,r),o=e.slice(n?r:r+1);t=r-1,""!==i&&"/"!==o&&(yield[i,o])}}(r))t.candidateRuleMap.has(e)&&(yield[t.candidateRuleMap.get(e),n?`-${i}`:i])}(n,t)){let s=[],a=new Map,[l,c]=r,u=1===l.length;for(let[e,r]of l){let n=[];if("function"==typeof r)for(let i of[].concat(r(c,{isOnlyPlugin:u}))){let[r,o]=Qi(i,t.postCssNodeCache);for(let t of r)n.push([{...e,options:{...e.options,...o}},t])}else if("DEFAULT"===c||"-DEFAULT"===c){let i=r,[o,s]=Qi(i,t.postCssNodeCache);for(let t of o)n.push([{...e,options:{...e.options,...s}},t])}if(n.length>0){let r=Array.from(ct(e.options?.types??[],c,e.options??{},t.tailwindConfig)).map((([e,t])=>t));r.length>0&&a.set(n,r),s.push(n)}}if(no(c)){if(s.length>1){let t=function(e){return 1===e.length?e[0]:e.find((e=>{let t=a.get(e);return e.some((([{options:e},r])=>!!Ji(r)&&e.types.some((({type:e,preferOnConflict:r})=>t.includes(e)&&r))))}))},[r,n]=s.reduce(((e,t)=>(t.some((([{options:e}])=>e.types.some((({type:e})=>"any"===e))))?e[0].push(t):e[1].push(t),e)),[[],[]]),i=t(n)??t(r);if(!i){let t=s.map((e=>new Set([...a.get(e)??[]])));for(let e of t)for(let r of e){let n=!1;for(let i of t)e!==i&&i.has(r)&&(i.delete(r),n=!0);n&&e.delete(r)}let r=[];for(let[n,i]of t.entries())for(let t of i){let i=s[n].map((([,e])=>e)).flat().map((e=>e.toString().split("\n").slice(1,-1).map((e=>e.trim())).map((e=>` ${e}`)).join("\n"))).join("\n\n");r.push(` Use \`${e.replace("[",`[${t}:`)}\` for \`${i.trim()}\``);break}D.warn([`The class \`${e}\` is ambiguous and matches multiple utilities.`,...r,`If this is content and not a class, replace it with \`${e.replace("[","[").replace("]","]")}\` to silence this warning.`]);continue}s=[i]}s=s.map((e=>e.filter((e=>Ji(e[1])))))}s=s.flat(),s=Array.from(Ki(s,n)),s=Gi(s,t),o&&(s=Yi(s,n));for(let e of i)s=Hi(e,s,t);for(let r of s)r[1].raws.tailwind={...r[1].raws.tailwind,candidate:e},r=eo(r,{context:t,candidate:e}),null!==r&&(yield r)}}function eo(e,{context:t,candidate:r}){if(!e[0].collectedFormats)return e;let n,i=!0;try{n=Di(e[0].collectedFormats,{context:t,candidate:r})}catch{return null}let o=Wt.root({nodes:[e[1].clone()]});return o.walkRules((e=>{if(!to(e))try{let i=Ri(e.selector,n,{candidate:r,context:t});if(null===i)return void e.remove();e.selector=i}catch{return i=!1,!1}})),i&&0!==o.nodes.length?(e[1]=o.nodes[0],e):null}function to(e){return e.parent&&"atrule"===e.parent.type&&"keyframes"===e.parent.name}function ro(e,t,r=!1){let n=[],i=function(e){return!0===e?e=>{to(e)||e.walkDecls((e=>{"rule"===e.parent.type&&!to(e.parent)&&(e.important=!0)}))}:"string"==typeof e?t=>{to(t)||(t.selectors=t.selectors.map((t=>Ni(t,e))))}:void 0}(t.tailwindConfig.important);for(let o of e){if(t.notClassCache.has(o))continue;if(t.candidateRuleCache.has(o)){n=n.concat(Array.from(t.candidateRuleCache.get(o)));continue}let e=Array.from(Zi(o,t));if(0===e.length){t.notClassCache.add(o);continue}t.classCache.set(o,e);let s=t.candidateRuleCache.get(o)??new Set;t.candidateRuleCache.set(o,s);for(let o of e){let[{sort:e,options:a},l]=o;if(a.respectImportant&&i){let e=Wt.root({nodes:[l.clone()]});e.walkRules(i),l=e.nodes[0]}let c=[e,r?l.clone():l];s.add(c),t.ruleCache.add(c),n.push(c)}}return n}function no(e){return e.startsWith("[")&&e.endsWith("]")}var io,oo,so,ao,lo=p((()=>{g(),kr(),io=m(on()),xn(),J(),Cn(),pt(),$(),vi(),Fi(),jn(),Qe(),No(),Ci(),be(),Y(),Wi(),oo=(0,io.default)((e=>e.first.filter((({type:e})=>"class"===e)).pop().value)),so=/^[a-z_-]/})),co=p((()=>{g(),ao={}}));var uo=p((()=>{g(),co(),vi()}));function po(e){return(e>0n)-(e<0n)}var fo=p((()=>{g()}));function ho(e,t){let r=0n,n=0n;for(let[i,o]of t)e&i&&(r|=i,n|=o);return e&~r|n}var mo=p((()=>{g()}));function go(e){let t=null;for(let r of e)t=t??r,t=t>r?t:r;return t}var yo,bo=p((()=>{g(),fo(),mo(),yo=class{constructor(){this.offsets={defaults:0n,base:0n,components:0n,utilities:0n,variants:0n,user:0n},this.layerPositions={defaults:0n,base:1n,components:2n,utilities:3n,user:4n,variants:5n},this.reservedVariantBits=0n,this.variantOffsets=new Map}create(e){return{layer:e,parentLayer:e,arbitrary:0n,variants:0n,parallelIndex:0n,index:this.offsets[e]++,propertyOffset:0n,property:"",options:[]}}arbitraryProperty(e){return{...this.create("utilities"),arbitrary:1n,property:e}}forVariant(e,t=0){let r=this.variantOffsets.get(e);if(void 0===r)throw new Error(`Cannot find offset for unknown variant ${e}`);return{...this.create("variants"),variants:r<e.startsWith("["))).sort((([e],[t])=>function(e,t){let r=e.length,n=t.length,i=re)).sort(((e,t)=>po(e-t)));return e.map((([,e],r)=>[e,t[r]])).filter((([e,t])=>e!==t))}remapArbitraryVariantOffsets(e){let t=this.recalculateVariantOffsets();return 0===t.length?e:e.map((e=>{let[r,n]=e;return r={...r,variants:ho(r.variants,t)},[r,n]}))}sortArbitraryProperties(e){let t=new Set;for(let[r]of e)1n===r.arbitrary&&t.add(r.property);if(0===t.size)return e;let r=Array.from(t).sort(),n=new Map,i=1n;for(let e of r)n.set(e,i++);return e.map((e=>{let[t,r]=e;return t={...t,propertyOffset:n.get(t.property)??0n},[t,r]}))}sort(e){return e=this.remapArbitraryVariantOffsets(e),(e=this.sortArbitraryProperties(e)).sort((([e],[t])=>po(this.compare(e,t))))}}}));function vo(e,t){let r=e.tailwindConfig.prefix;return"function"==typeof r?r(t):r+t}function wo({type:e="any",...t}){return{...t,types:[].concat(e).map((e=>Array.isArray(e)?{type:e[0],...e[1]}:{type:e,preferOnConflict:!1}))}}function xo(e){return Array.isArray(e)?e.flatMap((e=>Array.isArray(e)||Q(e)?vn(e):e)):xo([e])}function ko(e,t){return(0,$o.default)((e=>{let r=[];return t&&t(e),e.walkClasses((e=>{r.push(e.value)})),r})).transformSync(e)}function So(e){e.walkPseudos((e=>{":not"===e.value&&e.remove()}))}function Co(e){return xo(e).flatMap((e=>{let t=new Map,[r,n]=function(e,t={containsNonOnDemandable:!1},r=0){let n=[],i=[];"rule"===e.type?i.push(...e.selectors):"atrule"===e.type&&e.walkRules((e=>i.push(...e.selectors)));for(let e of i){let r=ko(e,So);0===r.length&&(t.containsNonOnDemandable=!0);for(let e of r)n.push(e)}return 0===r?[t.containsNonOnDemandable||0===n.length,n]:n}(e);return r&&n.unshift(yi),n.map((r=>(t.has(e)||t.set(e,e),[r,t.get(e)])))}))}function Ao(e){return e.startsWith("@")||e.includes("&")}function Oo(e){let t=function(e){let t=[],r="",n=0;for(let i=0;i0&&t.push(r.trim()),t=t.filter((e=>""!==e)),t}(e=e.replace(/\n+/g,"").replace(/\s{1,}/g," ").trim()).map((e=>{if(!e.startsWith("@"))return({format:t})=>t(e);let[,t,r]=/@(\S*)( .+|[({].*)?/g.exec(e);return({wrap:e})=>e(Wt.atRule({name:t,params:r?.trim()??""}))})).reverse();return e=>{for(let r of t)r(e)}}function Eo(e){return Uo.has(e)||Uo.set(e,new Map),Uo.get(e)}function _o(e,t){let n=!1,i=new Map;for(let o of e){if(!o)continue;let e=Nt.parse(o),s=e.hash?e.href.replace(e.hash,""):e.href;s=e.search?s.replace(e.search,""):s;let a=r.statSync(decodeURIComponent(s),{throwIfNoEntry:!1})?.mtimeMs;!a||((!t.has(o)||a>t.get(o))&&(n=!0),i.set(o,a))}return[n,i]}function To(e){e.walkAtRules((e=>{["responsive","variants"].includes(e.name)&&(To(e),e.before(e.nodes),e.remove())}))}function Po(e){let t=[];return e.each((e=>{"atrule"===e.type&&["responsive","variants"].includes(e.name)&&(e.name="layer",e.params="utilities")})),e.walkAtRules("layer",(e=>{if(To(e),"base"===e.params){for(let r of e.nodes)t.push((function({addBase:e}){e(r,{respectPrefix:!1})}));e.remove()}else if("components"===e.params){for(let r of e.nodes)t.push((function({addComponents:e}){e(r,{respectPrefix:!1,preserveSource:!0})}));e.remove()}else if("utilities"===e.params){for(let r of e.nodes)t.push((function({addUtilities:e}){e(r,{respectPrefix:!1,preserveSource:!0})}));e.remove()}})),t}function jo(e,t){!e.classCache.has(t)||(e.notClassCache.add(t),e.classCache.delete(t),e.applyClassCache.delete(t),e.candidateRuleMap.delete(t),e.candidateRuleCache.delete(t),e.stylesheetCache=null)}function Io(e,t=[],r=Wt.root()){let n={disposables:[],ruleCache:new Set,candidateRuleCache:new Map,classCache:new Map,applyClassCache:new Map,notClassCache:new Set(e.blocklist??[]),postCssNodeCache:new Map,candidateRuleMap:new Map,tailwindConfig:e,changedContent:t,variantMap:new Map,stylesheetCache:null,variantOptions:new Map,markInvalidUtilityCandidate:e=>jo(n,e),markInvalidUtilityNode:e=>function(e,t){let r=t.raws.tailwind.candidate;if(r){for(let t of e.ruleCache)t[1].raws.tailwind.candidate===r&&e.ruleCache.delete(t);jo(e,r)}}(n,e)},i=function(e,t){let r=Object.entries({...ii,...li}).map((([t,r])=>e.tailwindConfig.corePlugins.includes(t)?r:null)).filter(Boolean),n=e.tailwindConfig.plugins.map((e=>(e.__isOptionsFunction&&(e=e()),"function"==typeof e?e:e.handler))),i=Po(t),o=[ii.childVariant,ii.pseudoElementVariants,ii.pseudoClassVariants,ii.hasVariants,ii.ariaVariants,ii.dataVariants],s=[ii.supportsVariants,ii.reducedMotionVariants,ii.prefersContrastVariants,ii.screenVariants,ii.orientationVariants,ii.directionVariants,ii.darkVariants,ii.forcedColorsVariants,ii.printVariant];return("class"===e.tailwindConfig.darkMode||Array.isArray(e.tailwindConfig.darkMode)&&"class"===e.tailwindConfig.darkMode[0])&&(s=[ii.supportsVariants,ii.reducedMotionVariants,ii.prefersContrastVariants,ii.darkVariants,ii.screenVariants,ii.orientationVariants,ii.directionVariants,ii.forcedColorsVariants,ii.printVariant]),[...r,...o,...n,...s,...i]}(n,r);return function(e,t){let r=[],n=new Map;t.variantMap=n;let i=new yo;t.offsets=i;let o=new Set,s=function(e,t,{variantList:r,variantMap:n,offsets:i,classList:o}){function s(t,r){return t?(0,Do.default)(e,t,r):e}function a(e,r){return e===yi?yi:r.respectPrefix?t.tailwindConfig.prefix+e:e}let l=0,c={postcss:Wt,prefix:function(t){return kn(e.prefix,t)},e:An,config:s,theme:function(e,t,r={}){let n=F(e),i=s(["theme",...n],t);return sn(n[0])(i,r)},corePlugins:t=>Array.isArray(e.corePlugins)?e.corePlugins.includes(t):s(["corePlugins",t],!0),variants:()=>[],addBase(e){for(let[r,n]of Co(e)){let e=a(r,{}),o=i.create("base");t.candidateRuleMap.has(e)||t.candidateRuleMap.set(e,[]),t.candidateRuleMap.get(e).push([{sort:o,layer:"base"},n])}},addDefaults(e,r){let n={[`@defaults ${e}`]:r};for(let[e,r]of Co(n)){let n=a(e,{});t.candidateRuleMap.has(n)||t.candidateRuleMap.set(n,[]),t.candidateRuleMap.get(n).push([{sort:i.create("defaults"),layer:"defaults"},r])}},addComponents(e,r){r=Object.assign({},{preserveSource:!1,respectPrefix:!0,respectImportant:!1},Array.isArray(r)?{}:r);for(let[n,s]of Co(e)){let e=a(n,r);o.add(e),t.candidateRuleMap.has(e)||t.candidateRuleMap.set(e,[]),t.candidateRuleMap.get(e).push([{sort:i.create("components"),layer:"components",options:r},s])}},addUtilities(e,r){r=Object.assign({},{preserveSource:!1,respectPrefix:!0,respectImportant:!0},Array.isArray(r)?{}:r);for(let[n,s]of Co(e)){let e=a(n,r);o.add(e),t.candidateRuleMap.has(e)||t.candidateRuleMap.set(e,[]),t.candidateRuleMap.get(e).push([{sort:i.create("utilities"),layer:"utilities",options:r},s])}},matchUtilities:function(r,n){n=wo({respectPrefix:!0,respectImportant:!0,modifiers:!1,...n});let s=i.create("utilities");for(let i in r){let l=function(t,{isOnlyPlugin:r}){let[o,s,a]=lt(n.types,t,n,e);if(void 0===o)return[];if(!n.types.some((({type:e})=>e===s))){if(!r)return[];D.warn([`Unnecessary typehint \`${s}\` in \`${i}-${t}\`.`,`You can safely update it to \`${i}-${t.replace(s+":","")}\`.`])}if(!wi(o))return[];let l={get modifier(){return n.modifiers||D.warn(`modifier-used-without-options-for-${i}`,["Your plugin must set `modifiers: true` in its options to support modifiers."]),a}},c=V(e,"generalizedModifiers");return[].concat(c?u(o,l):u(o)).filter(Boolean).map((e=>({[Tn(i,t)]:e})))},c=a(i,n),u=r[i];o.add([c,n]);let d=[{sort:s,layer:"utilities",options:n},l];t.candidateRuleMap.has(c)||t.candidateRuleMap.set(c,[]),t.candidateRuleMap.get(c).push(d)}},matchComponents:function(r,n){n=wo({respectPrefix:!0,respectImportant:!1,modifiers:!1,...n});let s=i.create("components");for(let i in r){let l=function(t,{isOnlyPlugin:r}){let[o,s,a]=lt(n.types,t,n,e);if(void 0===o)return[];if(!n.types.some((({type:e})=>e===s))){if(!r)return[];D.warn([`Unnecessary typehint \`${s}\` in \`${i}-${t}\`.`,`You can safely update it to \`${i}-${t.replace(s+":","")}\`.`])}if(!wi(o))return[];let l={get modifier(){return n.modifiers||D.warn(`modifier-used-without-options-for-${i}`,["Your plugin must set `modifiers: true` in its options to support modifiers."]),a}},c=V(e,"generalizedModifiers");return[].concat(c?u(o,l):u(o)).filter(Boolean).map((e=>({[Tn(i,t)]:e})))},c=a(i,n),u=r[i];o.add([c,n]);let d=[{sort:s,layer:"components",options:n},l];t.candidateRuleMap.has(c)||t.candidateRuleMap.set(c,[]),t.candidateRuleMap.get(c).push(d)}},addVariant(e,i,o={}){i=[].concat(i).map((t=>{if("string"!=typeof t)return(r={})=>{let{args:n,modifySelectors:i,container:s,separator:a,wrap:l,format:c}=r,u=t(Object.assign({modifySelectors:i,container:s,separator:a},o.type===Ro.MatchVariant&&{args:n,wrap:l,format:c}));if("string"==typeof u&&!Ao(u))throw new Error(`Your custom variant \`${e}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`);return Array.isArray(u)?u.filter((e=>"string"==typeof e)).map((e=>Oo(e))):u&&"string"==typeof u&&Oo(u)(r)};if(!Ao(t))throw new Error(`Your custom variant \`${e}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`);return Oo(t)})),function(e,t,{before:r=[]}={}){if((r=[].concat(r)).length<=0)return void e.push(t);let n=e.length-1;for(let t of r){let r=e.indexOf(t);-1!==r&&(n=Math.min(n,r))}e.splice(n,0,t)}(r,e,o),n.set(e,i),t.variantOptions.set(e,o)},matchVariant(t,r,n){let i=n?.id??++l,o="@"===t,s=V(e,"generalizedModifiers");for(let[e,a]of Object.entries(n?.values??{}))"DEFAULT"!==e&&c.addVariant(o?`${t}${e}`:`${t}-${e}`,(({args:e,container:t})=>r(a,s?{modifier:e?.modifier,container:t}:{container:t})),{...n,value:a,id:i,type:Ro.MatchVariant,variantInfo:Mo.Base});let a="DEFAULT"in(n?.values??{});c.addVariant(t,(({args:e,container:t})=>e?.value!==bi||a?r(e?.value===bi?n.values.DEFAULT:e?.value??("string"==typeof e?e:""),s?{modifier:e?.modifier,container:t}:{container:t}):null),{...n,id:i,type:Ro.MatchVariant,variantInfo:Mo.Dynamic})}};return c}(t.tailwindConfig,t,{variantList:r,variantMap:n,offsets:i,classList:o});for(let t of e)if(Array.isArray(t))for(let e of t)e(s);else t?.(s);i.recordVariants(r,(e=>n.get(e).length));for(let[e,r]of n.entries())t.variantMap.set(e,r.map(((t,r)=>[i.forVariant(e,r),t])));let a=(t.tailwindConfig.safelist??[]).filter(Boolean);if(a.length>0){let e=[];for(let r of a)"string"!=typeof r?r instanceof RegExp?D.warn("root-regex",["Regular expressions in `safelist` work differently in Tailwind CSS v3.0.","Update your `safelist` configuration to eliminate this warning.","https://tailwindcss.com/docs/content-configuration#safelisting-classes"]):e.push(r):t.changedContent.push({content:r,extension:"html"});if(e.length>0){let r=new Map,n=t.tailwindConfig.prefix.length,i=e.some((e=>e.pattern.source.includes("!")));for(let s of o){let o=Array.isArray(s)?(()=>{let[e,r]=s,o=Object.keys(r?.values??{}).map((t=>Pn(e,t)));return r?.supportsNegativeValues&&(o=[...o,...o.map((e=>"-"+e))],o=[...o,...o.map((e=>e.slice(0,n)+"-"+e.slice(n)))]),r.types.some((({type:e})=>"color"===e))&&(o=[...o,...o.flatMap((e=>Object.keys(t.tailwindConfig.theme.opacity).map((t=>`${e}/${t}`))))]),i&&r?.respectImportant&&(o=[...o,...o.map((e=>"!"+e))]),o})():[s];for(let n of o)for(let{pattern:i,variants:o=[]}of e)if(i.lastIndex=0,r.has(i)||r.set(i,0),i.test(n)){r.set(i,r.get(i)+1),t.changedContent.push({content:n,extension:"html"});for(let e of o)t.changedContent.push({content:e+t.tailwindConfig.separator+n,extension:"html"})}}for(let[e,t]of r.entries())0===t&&D.warn([`The safelist pattern \`${e}\` doesn't match any Tailwind CSS classes.`,"Fix this pattern or remove it from your `safelist` configuration.","https://tailwindcss.com/docs/content-configuration#safelisting-classes"])}}let l=[].concat(t.tailwindConfig.darkMode??"media")[1]??"dark",c=[vo(t,l),vo(t,"group"),vo(t,"peer")];t.getClassOrder=function(e){let r=[...e].sort(((e,t)=>e===t?0:e[e,null]))),i=ro(new Set(r),t,!0);i=t.offsets.sort(i);let o=BigInt(c.length);for(let[,e]of i){let t=e.raws.tailwind.candidate;n.set(t,n.get(t)??o++)}return e.map((e=>{let t=n.get(e)??null,r=c.indexOf(e);return null===t&&-1!==r&&(t=BigInt(r)),[e,t]}))},t.getClassList=function(e={}){let r=[];for(let n of o)if(Array.isArray(n)){let[i,o]=n,s=[],a=Object.keys(o?.modifiers??{});o?.types?.some((({type:e})=>"color"===e))&&a.push(...Object.keys(t.tailwindConfig.theme.opacity??{}));let l={modifiers:a},c=e.includeMetadata&&a.length>0;for(let[e,t]of Object.entries(o?.values??{})){if(null==t)continue;let n=Pn(i,e);if(r.push(c?[n,l]:n),o?.supportsNegativeValues&&k(t)){let t=Pn(i,`-${e}`);s.push(c?[t,l]:t)}}r.push(...s)}else r.push(n);return r},t.getVariants=function(){let e=Math.random().toString(36).substring(7).toUpperCase(),r=[];for(let[n,i]of t.variantOptions.entries())i.variantInfo!==Mo.Base&&r.push({name:n,isArbitrary:i.type===Symbol.for("MATCH_VARIANT"),values:Object.keys(i.values??{}),hasDash:"@"!==n,selectors({modifier:r,value:o}={}){let s=`TAILWINDPLACEHOLDER${e}`,a=Wt.rule({selector:`.${s}`}),l=Wt.root({nodes:[a.clone()]}),c=l.toString(),u=(t.variantMap.get(n)??[]).flatMap((([e,t])=>t)),d=[];for(let e of u){let n=[],s={args:{modifier:r,value:i.values?.[o]??o},separator:t.tailwindConfig.separator,modifySelectors:e=>(l.each((t=>{"rule"===t.type&&(t.selectors=t.selectors.map((t=>e({get className(){return qi(t)},selector:t}))))})),l),format(e){n.push(e)},wrap(e){n.push(`@${e.name} ${e.params} { & }`)},container:l},a=e(s);if(n.length>0&&d.push(n),Array.isArray(a))for(let e of a)n=[],e(s),d.push(n)}let p=[];c!==l.toString()&&(l.walkRules((e=>{let r=e.selector,i=(0,$o.default)((e=>{e.walkClasses((e=>{e.value=`${n}${t.tailwindConfig.separator}${e.value}`}))})).processSync(r);p.push(r.replace(i,"&").replace(s,"&"))})),l.walkAtRules((e=>{p.push(`@${e.name} (${e.params}) { & }`)})));let f=!(o in(i.values??{})),h=i[Bo]??{},m=!(f||!1===h.respectPrefix);d=d.map((e=>e.map((e=>({format:e,respectPrefix:m}))))),p=p.map((e=>({format:e,respectPrefix:m})));let g={candidate:s,context:t},y=d.map((e=>Ri(`.${s}`,Di(e,g),g).replace(`.${s}`,"&").replace("{ & }","").trim()));return p.length>0&&y.push(Di(p,g).toString().replace(`.${s}`,"&")),y}});return r}}(i,n),n}var Do,$o,Bo,Ro,Mo,Uo,zo,Lo,Fo,No=p((()=>{g(),y(),qt(),kr(),Do=m(Sr()),$o=m(on()),cn(),xn(),Cn(),J(),En(),jn(),pt(),ui(),vi(),vi(),N(),$(),C(),Ci(),lo(),uo(),bo(),Y(),Fi(),Bo=Symbol(),Ro={AddVariant:Symbol.for("ADD_VARIANT"),MatchVariant:Symbol.for("MATCH_VARIANT")},Mo={Base:1,Dynamic:2},Uo=new WeakMap,zo=fi,Lo=hi,Fo=mi}));function Vo(t){return t.ignore?[]:t.glob?"true"===e.env.ROLLUP_WATCH?[{type:"dependency",file:t.base}]:[{type:"dir-dependency",dir:t.base,glob:t.glob}]:[{type:"dependency",file:t.base}]}var Wo=p((()=>{g()}));function qo(e,t){return{handler:e,config:t}}var Go,Yo=p((()=>{g(),qo.withOptions=function(e,t=(()=>({}))){let r=function(r){return{__options:r,handler:e(r),config:t(r)}};return r.__isOptionsFunction=!0,r.__pluginFunction=e,r.__configFunction=t,r},Go=qo})),Ho={};h(Ho,{default:()=>Qo});var Qo,Jo=p((()=>{g(),Yo(),Qo=Go})),Xo=f(((e,t)=>{g();var r=(Jo(),Ho).default,n={overflow:"hidden",display:"-webkit-box","-webkit-box-orient":"vertical"},i=r((function({matchUtilities:e,addUtilities:t,theme:r,variants:i}){e({"line-clamp":e=>({...n,"-webkit-line-clamp":`${e}`})},{values:r("lineClamp")}),t([{".line-clamp-none":{"-webkit-line-clamp":"unset"}}],i("lineClamp"))}),{theme:{lineClamp:{1:"1",2:"2",3:"3",4:"4",5:"5",6:"6"}},variants:{lineClamp:["responsive"]}});t.exports=i}));function Ko(e){0===e.content.files.length&&D.warn("content-problems",["The `content` option in your Tailwind CSS configuration is missing or empty.","Configure your content sources or your generated CSS will be missing styles.","https://tailwindcss.com/docs/content-configuration"]);try{let t=Xo();e.plugins.includes(t)&&(D.warn("line-clamp-in-core",["As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.","Remove it from the `plugins` array in your configuration to eliminate this warning."]),e.plugins=e.plugins.filter((e=>e!==t)))}catch{}return e}var Zo,es,ts,rs,ns=p((()=>{g(),$()})),is=p((()=>{g(),Zo=()=>!1})),os=p((()=>{g(),es={sync:e=>[].concat(e),generateTasks:e=>[{dynamic:!1,base:".",negative:[],positive:[].concat(e),patterns:[].concat(e)}],escapePath:e=>e}})),ss=p((()=>{g(),ts=e=>e})),as=p((()=>{g(),rs=()=>""})),ls=p((()=>{g(),as()}));function cs(e,t){let r={original:e,base:e,ignore:t,pattern:e,glob:null};return Zo(e)&&Object.assign(r,function(e){let t=e,r=rs(e);return"."!==r&&(t=e.substr(r.length),"/"===t.charAt(0)&&(t=t.substr(1))),"./"===t.substr(0,2)?t=t.substr(2):"/"===t.charAt(0)&&(t=t.substr(1)),{base:r,glob:t}}(e)),r}function us(e){let t=ts(e.base);return t=es.escapePath(t),e.pattern=e.glob?`${t}/${e.glob}`:t,e.pattern=e.ignore?`!${e.pattern}`:e.pattern,e}function ds(e){let t=[e];try{let n=r.realpathSync(e.base);n!==e.base&&t.push({...e,base:n})}catch{}return t}var ps=p((()=>{g(),y(),Rt(),is(),os(),ss(),ls(),vi()})),fs=p((()=>{g()}));function*hs(e,t,n,i=Bt.extname(e)){let o=function(e,t){for(let n of t){let t=`${e}${n}`;if(r.existsSync(t)&&r.statSync(t).isFile())return t}for(let n of t){let t=`${e}/index${n}`;if(r.existsSync(t))return t}return null}(Bt.resolve(t,e),ms.includes(i)?gs:ys);if(null===o||n.has(o))return;n.add(o),yield o,t=Bt.dirname(o),i=Bt.extname(o);let s=r.readFileSync(o,"utf-8");for(let e of[...s.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),...s.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),...s.matchAll(/require\(['"`](.+)['"`]\)/gi)])!e[1].startsWith(".")||(yield*hs(e[1],t,n,i))}var ms,gs,ys,bs=p((()=>{g(),y(),Rt(),ms=[".js",".cjs",".mjs"],gs=["",".js",".cjs",".mjs",".ts",".cts",".mts",".jsx",".tsx"],ys=["",".ts",".cts",".mts",".tsx",".js",".cjs",".mjs",".jsx"]}));function vs(e){let t=function(e){return Mt(e)&&void 0===e.config&&!function(e){return 0===Object.keys(e).length}(e)?null:Mt(e)&&void 0!==e.config&&Ut(e.config)?Bt.resolve(e.config):Mt(e)&&void 0!==e.config&&Mt(e.config)?null:Ut(e)?Bt.resolve(e):function(){for(let e of zt)try{let t=Bt.resolve(e);return r.accessSync(t),t}catch(e){}return null}()}(e);if(null!==t){let[e,n,i,o]=ks.get(t)||[],s=function(e){return null===e?new Set:new Set(hs(e,Bt.dirname(e),new Set))}(t),a=!1,l=new Map;for(let e of s){let t=r.statSync(e).mtimeMs;l.set(e,t),(!o||!o.has(e)||t>o.get(e))&&(a=!0)}if(!a)return[e,t,n,i];for(let e of s)delete d.cache[e];let c=Ko(It(void 0)),u=w(c);return ks.set(t,[c,u,s,l]),[c,t,u,s]}let n=It(e?.config??e??{});return n=Ko(n),[n,null,w(n),[]]}function ws(e){return({tailwindDirectives:t,registerDependency:n})=>(i,o)=>{let[s,a,l,c]=vs(e),u=new Set(c);if(t.size>0){u.add(o.opts.from);for(let e of o.messages)"dependency"===e.type&&u.add(e.file)}let[d,,p]=function(e,t,r,n,i,o){let s,a=t.opts.from,l=null!==n;if(pi.DEBUG&&console.log("Source path:",a),l&&zo.has(a))s=zo.get(a);else if(Lo.has(i)){let e=Lo.get(i);Fo.get(e).add(a),zo.set(a,e),s=e}let c=function(e,t){let r=t.toString();if(!r.includes("@tailwind"))return!1;let n=gi.get(e),i=function(e){try{return ao.createHash("md5").update(e,"utf-8").digest("binary")}catch(e){return""}}(r),o=n!==i;return gi.set(e,i),o}(a,e);if(s){let[e,t]=_o([...o],Eo(s));if(!e&&!c)return[s,!1,t]}if(zo.has(a)){let e=zo.get(a);if(Fo.has(e)&&(Fo.get(e).delete(a),0===Fo.get(e).size)){Fo.delete(e);for(let[t,r]of Lo)r===e&&Lo.delete(t);for(let t of e.disposables.splice(0))t(e)}}pi.DEBUG&&console.log("Setting up new context...");let u=Io(r,[],e);Object.assign(u,{userConfigPath:n});let[,d]=_o([...o],Eo(u));return Lo.set(i,u),zo.set(a,u),Fo.has(u)||Fo.set(u,new Set),Fo.get(u).add(a),[u,!0,d]}(i,o,s,a,l,u),f=Eo(d),h=function(e,t){if(Ss.has(e))return Ss.get(e);let r=function(e,t){let r=t.content.files;r=r.filter((e=>"string"==typeof e)),r=r.map(ts);let n=es.generateTasks(r),i=[],o=[];for(let e of n)i.push(...e.positive.map((e=>cs(e,!1)))),o.push(...e.negative.map((e=>cs(e,!0))));let s=[...i,...o];return s=function(e,t){let r=[];return e.userConfigPath&&e.tailwindConfig.content.relative&&(r=[Bt.dirname(e.userConfigPath)]),t.map((e=>(e.base=Bt.resolve(...r,e.base),e)))}(e,s),s=s.flatMap(ds),s=s.map(us),s}(e,t);return Ss.set(e,r).get(e)}(d,s);if(t.size>0){for(let e of h)for(let t of Vo(e))n(t);let[e,t]=function(e,t,n){let i=e.tailwindConfig.content.files.filter((e=>"string"==typeof e.raw)).map((({raw:e,extension:t="html"})=>({content:e,extension:t}))),[o,s]=function(e,t){let n=e.map((e=>e.pattern)),i=new Map,o=new Set;pi.DEBUG&&console.time("Finding changed files");let s=es.sync(n,{absolute:!0});for(let e of s){let n=t.get(e)||-1/0,s=r.statSync(e).mtimeMs;s>n&&(o.add(e),i.set(e,s))}return pi.DEBUG&&console.timeEnd("Finding changed files"),[o,i]}(t,n);for(let e of o){let t=Bt.extname(e).slice(1);i.push({file:e,extension:t})}return[i,s]}(d,h,f);for(let t of e)d.changedContent.push(t);for(let[e,r]of t.entries())p.set(e,r)}for(let e of c)n({type:"dependency",file:e});for(let[e,t]of p.entries())f.set(e,t);return d}}var xs,ks,Ss,Cs=p((()=>{g(),y(),xs=m(b()),x(),Dt(),Lt(),No(),Wo(),ns(),ps(),fs(),bs(),ks=new xs.default({maxSize:100}),Ss=new WeakMap})),As=p((()=>{g(),$()}));function Os(e,t=void 0,r=void 0){return e.map((e=>{let n=e.clone();return void 0!==r&&(n.raws.tailwind={...n.raws.tailwind,...r}),void 0!==t&&Es(n,(e=>{if(!0===e.raws.tailwind?.preserveSource&&e.source)return!1;e.source=t})),n}))}function Es(e,t){!1!==t(e)&&e.each?.((e=>Es(e,t)))}var _s=p((()=>{g()}));function Ts(e){return(e=(e=Array.isArray(e)?e:[e]).map((e=>e instanceof RegExp?e.source:e))).join("")}function Ps(e){return new RegExp(Ts(e),"g")}function js(e){return`(?:${e.map(Ts).join("|")})`}function Is(e){return`(?:${Ts(e)})?`}function Ds(e){return e&&Bs.test(e)?e.replace($s,"\\$&"):e||""}var $s,Bs,Rs=p((()=>{g(),$s=/[\\^$.*+?()[\]{}|]/g,Bs=RegExp($s.source)}));function Ms(e){let t=Array.from(function*(e){let t=e.tailwindConfig.separator,r=""!==e.tailwindConfig.prefix?Is(Ps([/-?/,Ds(e.tailwindConfig.prefix)])):"",n=js([/\[[^\s:'"`]+:[^\s\[\]]+\]/,/\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,Ps([js([/-?(?:\w+)/,/@(?:\w+)/]),Is(js([Ps([js([/-(?:\w+-)*\['[^\s]+'\]/,/-(?:\w+-)*\["[^\s]+"\]/,/-(?:\w+-)*\[`[^\s]+`\]/,/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/]),/(?![{([]])/,/(?:\/[^\s'"`\\><$]*)?/]),Ps([js([/-(?:\w+-)*\['[^\s]+'\]/,/-(?:\w+-)*\["[^\s]+"\]/,/-(?:\w+-)*\[`[^\s]+`\]/,/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/]),/(?![{([]])/,/(?:\/[^\s'"`\\$]*)?/]),/[-\/][^\s'"`\\$={><]*/]))])]),i=[js([Ps([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/,t]),Ps([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/[\w_-]+/,t]),Ps([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/,t]),Ps([/[^\s"'`\[\\]+/,t])]),js([Ps([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/[\w_-]+/,t]),Ps([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/,t]),Ps([/[^\s`\[\\]+/,t])])];for(let e of i)yield Ps(["((?=((",e,")+))\\2)?",/!?/,r,n])}(e));return e=>{let r=[];for(let n of t)for(let t of e.match(n)??[])r.push(Us(t));for(let e of r.slice()){let t=ye(e,".");for(let e=0;e=t.length-1){r.push(n);continue}let i=parseInt(t[e+1]);isNaN(i)?r.push(n):e++}}return r}}function Us(e){if(!e.includes("-["))return e;let t=0,r=[],n=e.matchAll(zs);n=Array.from(n).flatMap((e=>{let[,...t]=e;return t.map(((t,r)=>Object.assign([],e,{index:e.index+r,0:t})))}));for(let i of n){let n=i[0],o=r[r.length-1];if(n===o?r.pop():("'"===n||'"'===n||"`"===n)&&r.push(n),!o){if("["===n){t++;continue}if("]"===n){t--;continue}if(t<0)return e.substring(0,i.index-1);if(0===t&&!Ls.test(n))return e.substring(0,i.index)}}return e}var zs,Ls,Fs=p((()=>{g(),Rs(),be(),zs=/([\[\]'"`])([^\[\]'"`])?/g,Ls=/[^"'`\s<>\]]+/}));function Ns(e,t){let r=e.tailwindConfig.content.extract;return r[t]||r.DEFAULT||Ys[t]||Ys.DEFAULT(e)}function Vs(e,t){let r=e.content.transform;return r[t]||r.DEFAULT||Hs[t]||Hs.DEFAULT}function Ws(e,t,r,n){Qs.has(t)||Qs.set(t,new qs.default({maxSize:25e3}));for(let i of e.split("\n"))if(i=i.trim(),!n.has(i))if(n.add(i),Qs.get(t).has(i))for(let e of Qs.get(t).get(i))r.add(e);else{let e=t(i).filter((e=>"!*"!==e)),n=new Set(e);for(let e of n)r.add(e);Qs.get(t).set(i,n)}}var qs,Gs,Ys,Hs,Qs,Js=p((()=>{g(),y(),qs=m(b()),vi(),lo(),$(),_s(),Fs(),Gs=pi,Ys={DEFAULT:Ms},Hs={DEFAULT:e=>e,svelte:e=>e.replace(/(?:^|\s)class:/g," ")},Qs=new WeakMap}));function Xs(e){let t=new Map;Wt.root({nodes:[e.clone()]}).walkRules((e=>{(0,aa.default)((e=>{e.walkClasses((e=>{let r=e.parent.toString(),n=t.get(r);n||t.set(r,n=new Set),n.add(e.value)}))})).processSync(e.selector)}));let r=Array.from(t.values(),(e=>Array.from(e))),n=r.flat();return Object.assign(n,{groups:r})}function Ks(e){return la.astSync(e)}function Zs(e,t){let r=new Set;for(let n of e)r.add(n.split(t).pop());return Array.from(r)}function ea(e,t){let r=e.tailwindConfig.prefix;return"function"==typeof r?r(t):r+t}function*ta(e){for(yield e;e.parent;)yield e.parent,e=e.parent}function ra(e,t={}){let r=e.nodes;e.nodes=[];let n=e.clone(t);return e.nodes=r,n}function na(e,t){for(let r of e){if(t.notClassCache.has(r)||t.applyClassCache.has(r))continue;if(t.classCache.has(r)){t.applyClassCache.set(r,t.classCache.get(r).map((([e,t])=>[e,t.clone()])));continue}let e=Array.from(Zi(r,t));0!==e.length?t.applyClassCache.set(r,e):t.notClassCache.add(r)}return t.applyClassCache}function ia(e){let t=e.split(/[\s\t\n]+/g);return"!important"===t[t.length-1]?[t.slice(0,-1),!0]:[t,!1]}function oa(e,t,r){let n=new Set,i=[];if(e.walkAtRules("apply",(e=>{let[t]=ia(e.params);for(let e of t)n.add(e);i.push(e)})),0===i.length)return;let o=function(e){return{get:t=>e.flatMap((e=>e.get(t)||[])),has:t=>e.some((e=>e.has(t)))}}([r,na(n,t)]);function s(e,t,r){let n=Ks(e),i=Ks(t),o=Ks(`.${An(r)}`).nodes[0].nodes[0];return n.each((e=>{let t=new Set;i.each((r=>{let n=!1;(r=r.clone()).walkClasses((i=>{i.value===o.value&&(n||(i.replaceWith(...e.nodes.map((e=>e.clone()))),t.add(r),n=!0))}))}));for(let e of t){let t=[[]];for(let r of e.nodes)"combinator"===r.type?(t.push(r),t.push([])):t[t.length-1].push(r);e.nodes=[];for(let r of t)Array.isArray(r)&&r.sort(((e,t)=>"tag"===e.type&&"class"===t.type?-1:"class"===e.type&&"tag"===t.type?1:"class"===e.type&&"pseudo"===t.type&&t.value.startsWith("::")?-1:"pseudo"===e.type&&e.value.startsWith("::")&&"class"===t.type?1:0)),e.nodes=e.nodes.concat(r)}e.replaceWith(...t)})),n.toString()}let a=new Map;for(let e of i){let[r]=a.get(e.parent)||[[],e.source];a.set(e.parent,[r,e.source]);let[n,i]=ia(e.params);if("atrule"===e.parent.type){if("screen"===e.parent.name){let t=e.parent.params;throw e.error(`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${n.map((e=>`${t}:${e}`)).join(" ")} instead.`)}throw e.error(`@apply is not supported within nested at-rules like @${e.parent.name}. You can fix this by un-nesting @${e.parent.name}.`)}for(let s of n){if([ea(t,"group"),ea(t,"peer")].includes(s))throw e.error(`@apply should not be used with the '${s}' utility`);if(!o.has(s))throw e.error(`The \`${s}\` class does not exist. If \`${s}\` is a custom class, make sure it is defined within a \`@layer\` directive.`);let n=o.get(s);for(let[,t]of n)"atrule"!==t.type&&t.walkRules((()=>{throw e.error([`The \`${s}\` class cannot be used with \`@apply\` because \`@apply\` does not currently support nested CSS.`,"Rewrite the selector without nesting or configure the `tailwindcss/nesting` plugin:","https://tailwindcss.com/docs/using-with-preprocessors#nesting"].join("\n"))}));r.push([s,i,n])}}for(let[e,[r,n]]of a){let i=[];for(let[o,a,l]of r){let r=[o,...Zs([o],t.tailwindConfig.separator)];for(let[c,u]of l){let l=Xs(e),d=Xs(u);if(d=d.groups.filter((e=>e.some((e=>r.includes(e))))).flat(),d=d.concat(Zs(d,t.tailwindConfig.separator)),l.some((e=>d.includes(e))))throw u.error(`You cannot \`@apply\` the \`${o}\` utility here because it creates a circular dependency.`);let p=Wt.root({nodes:[u.clone()]});p.walk((e=>{e.source=n})),("atrule"!==u.type||"atrule"===u.type&&"keyframes"!==u.name)&&p.walkRules((r=>{if(!Xs(r).some((e=>e===o)))return void r.remove();let n="string"==typeof t.tailwindConfig.important?t.tailwindConfig.important:null,i=void 0!==e.raws.tailwind&&n&&0===e.selector.indexOf(n)?e.selector.slice(n.length):e.selector;""===i&&(i=e.selector),r.selector=s(i,r.selector,o),n&&i!==e.selector&&(r.selector=Ni(r.selector,n)),r.walkDecls((e=>{e.important=c.important||a}));let l=(0,aa.default)().astSync(r.selector);l.each((e=>Ai(e))),r.selector=l.toString()})),p.nodes[0]&&i.push([c.sort,p.nodes[0]])}}let o=t.offsets.sort(i).map((e=>e[1]));e.after(o)}for(let e of i)e.parent.nodes.length>1?e.remove():e.parent.remove();oa(e,t,r)}function sa(e){return t=>{let r=function(e){let t=null;return{get:r=>(t=t||e(),t.get(r)),has:r=>(t=t||e(),t.has(r))}}((()=>function(e,t){let r=new Map;return e.walkRules((e=>{for(let t of ta(e))if(void 0!==t.raws.tailwind?.layer)return;let n=function(e){for(let t of ta(e))if(e!==t){if("root"===t.type)break;e=ra(t,{nodes:[e]})}return e}(e),i=t.offsets.create("user");for(let t of Xs(e)){let e=r.get(t)||[];r.set(t,e),e.push([{layer:"user",sort:i,important:!1},n])}})),r}(t,e)));oa(t,e,r)}}var aa,la,ca=p((()=>{g(),kr(),aa=m(on()),lo(),En(),Wi(),Ii(),la=(0,aa.default)()})),ua=f(((e,t)=>{g(),function(){"use strict";function e(t,r,i){if(!t)return null;e.caseSensitive||(t=t.toLowerCase());var o,s=null===e.threshold?null:e.threshold*t.length,a=e.thresholdAbsolute;o=null!==s&&null!==a?Math.min(s,a):null!==s?s:null!==a?a:null;var l,c,u,d,p=r.length;for(d=0;dn)return n+1;var s,a,l,c,u,d=[];for(s=0;s<=o;s++)d[s]=[s];for(a=0;a<=i;a++)d[0][a]=a;for(s=1;s<=o;s++){for(l=r,c=1,s>n&&(c=s-n),(u=o+1)>n+s&&(u=n+s),a=1;a<=i;a++)au?d[s][a]=n+1:t.charAt(s-1)===e.charAt(a-1)?d[s][a]=d[s-1][a-1]:d[s][a]=Math.min(d[s-1][a-1]+1,Math.min(d[s][a-1]+1,d[s-1][a]+1)),d[s][a]n)return n+1}return d[o][i]}}()})),da=f(((e,t)=>{g();var r="(".charCodeAt(0),n=")".charCodeAt(0),i="'".charCodeAt(0),o='"'.charCodeAt(0),s="\\".charCodeAt(0),a="/".charCodeAt(0),l=",".charCodeAt(0),c=":".charCodeAt(0),u="*".charCodeAt(0),d="u".charCodeAt(0),p="U".charCodeAt(0),f="+".charCodeAt(0),h=/^[a-f0-9?-]+$/i;t.exports=function(e){for(var t,m,g,y,b,v,w,x,k,S=[],C=e,A=0,O=C.charCodeAt(A),E=C.length,_=[{nodes:S}],T=0,P="",j="",I="";A{g(),t.exports=function e(t,r,n){var i,o,s,a;for(i=0,o=t.length;i{function r(e,t){var r,i,o=e.type,s=e.value;return t&&void 0!==(i=t(e))?i:"word"===o||"space"===o?s:"string"===o?(r=e.quote||"")+s+(e.unclosed?"":r):"comment"===o?"/*"+s+(e.unclosed?"":"*/"):"div"===o?(e.before||"")+s+(e.after||""):Array.isArray(e.nodes)?(r=n(e.nodes,t),"function"!==o?r:s+"("+(e.before||"")+r+(e.after||"")+(e.unclosed?"":")")):s}function n(e,t){var n,i;if(Array.isArray(e)){for(n="",i=e.length-1;~i;i-=1)n=r(e[i],t)+n;return n}return r(e,t)}g(),t.exports=n})),ha=f(((e,t)=>{g();var r="-".charCodeAt(0),n="+".charCodeAt(0),i=".".charCodeAt(0),o="e".charCodeAt(0),s="E".charCodeAt(0);t.exports=function(e){var t,a,l,c=0,u=e.length;if(0===u||!function(e){var t,o=e.charCodeAt(0);if(o===n||o===r){if((t=e.charCodeAt(1))>=48&&t<=57)return!0;var s=e.charCodeAt(2);return t===i&&s>=48&&s<=57}return o===i?(t=e.charCodeAt(1))>=48&&t<=57:o>=48&&o<=57}(e))return!1;for(((t=e.charCodeAt(c))===n||t===r)&&c++;c57);)c+=1;if(t=e.charCodeAt(c),a=e.charCodeAt(c+1),t===i&&a>=48&&a<=57)for(c+=2;c57);)c+=1;if(t=e.charCodeAt(c),a=e.charCodeAt(c+1),l=e.charCodeAt(c+2),(t===o||t===s)&&(a>=48&&a<=57||(a===n||a===r)&&l>=48&&l<=57))for(c+=a===n||a===r?3:2;c57);)c+=1;return{number:e.slice(0,c),unit:e.slice(c)}}})),ma=f(((e,t)=>{g();var r=da(),n=pa(),i=fa();function o(e){return this instanceof o?(this.nodes=r(e),this):new o(e)}o.prototype.toString=function(){return Array.isArray(this.nodes)?i(this.nodes):""},o.prototype.walk=function(e,t){return n(this.nodes,e,t),this},o.unit=ha(),o.walk=n,o.stringify=i,t.exports=o}));function ga(e){return"object"==typeof e&&null!==e}function ya(e){return"string"==typeof e?e:e.reduce(((e,t,r)=>t.includes(".")?`${e}[${t}]`:0===r?t:`${e}.${t}`),"")}function ba(e){return e.map((e=>`'${e}'`)).join(", ")}function va(e){return ba(Object.keys(e))}function wa(e,t,r,n={}){let i=Array.isArray(t)?ya(t):t.replace(/^['"]+|['"]+$/g,""),o=Array.isArray(t)?t:F(i),s=(0,Sa.default)(e.theme,o,r);if(void 0===s){let t=`'${i}' does not exist in your theme config.`,r=o.slice(0,-1),n=(0,Sa.default)(e.theme,r);if(ga(n)){let i=Object.keys(n).filter((t=>wa(e,[...r,t]).isValid)),s=(0,Ca.default)(o[o.length-1],i);s?t+=` Did you mean '${ya([...r,s])}'?`:i.length>0&&(t+=` '${ya(r)}' has the following valid keys: ${ba(i)}`)}else{let r=function(e,t){let r=F(t);do{if(r.pop(),void 0!==(0,Sa.default)(e,r))break}while(r.length);return r.length?r:void 0}(e.theme,i);if(r){let n=(0,Sa.default)(e.theme,r);ga(n)?t+=` '${ya(r)}' has the following keys: ${va(n)}`:t+=` '${ya(r)}' is not an object.`}else t+=` Your theme has the following top-level keys: ${va(e.theme)}`}return{isValid:!1,error:t}}if(!("string"==typeof s||"number"==typeof s||"function"==typeof s||s instanceof String||s instanceof Number||Array.isArray(s))){let t=`'${i}' was found but does not resolve to a string.`;if(ga(s)){let r=Object.keys(s).filter((t=>wa(e,[...o,t]).isValid));r.length&&(t+=` Did you mean something like '${ya([...o,r[0]])}'?`)}return{isValid:!1,error:t}}let[a]=o;return{isValid:!0,value:sn(a)(s,n)}}function xa(e,t,r){if("function"===t.type&&void 0!==r[t.value]){let n=function(e,t,r){t=t.map((t=>xa(e,t,r)));let n=[""];for(let e of t)"div"===e.type&&","===e.value?n.push(""):n[n.length-1]+=Aa.default.stringify(e);return n}(e,t.nodes,r);t.type="word",t.value=r[t.value](e,...n)}return t}function ka(e){let t=e.tailwindConfig,r={theme:(r,n,...i)=>{let{isValid:o,value:s,error:a,alpha:l}=function(e,t,r){let n=Array.from(function*(e){let t,r=(e=e.replace(/^['"]+|['"]+$/g,"")).match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/);yield[e,void 0],r&&(e=r[1],t=r[2],yield[e,t])}(t)).map((([t,n])=>Object.assign(wa(e,t,r,{opacityValue:n}),{resolvedPath:t,alpha:n})));return n.find((e=>e.isValid))??n[0]}(t,n,i.length?i:void 0);if(!o){let t=r.parent,n=t?.raws.tailwind?.candidate;if(t&&void 0!==n)return e.markInvalidUtilityNode(t),t.remove(),void D.warn("invalid-theme-key-in-class",[`The utility \`${n}\` contains an invalid theme value and was not generated.`]);throw r.error(a)}let c=nt(s);return(void 0!==l||void 0!==c&&"function"==typeof c)&&(void 0===l&&(l=1),s=he(c,l,c)),s},screen:(e,r)=>{r=r.replace(/^['"]+/g,"").replace(/['"]+$/g,"");let n=Kn(t.theme.screens).find((({name:e})=>e===r));if(!n)throw e.error(`The '${r}' screen does not exist in your theme.`);return $n(n)}};return e=>{e.walk((e=>{let t=Oa[e.type];void 0!==t&&(e[t]=function(e,t,r){return Object.keys(r).some((e=>t.includes(`${e}(`)))?(0,Aa.default)(t).walk((t=>{xa(e,t,r)})).toString():t}(e,e[t],r))}))}}var Sa,Ca,Aa,Oa,Ea=p((()=>{g(),Sa=m(Sr()),Ca=m(ua()),cn(),Aa=m(ma()),ri(),Hn(),N(),ge(),pt(),$(),Oa={atrule:"params",decl:"value"}})),_a=p((()=>{g(),ri(),Hn()}));function Ta(e){return Da.has(e)||Da.set(e,Ia.transformSync(e)),Da.get(e)}var Pa,ja,Ia,Da,$a=p((()=>{g(),kr(),Pa=m(on()),Y(),ja={id:e=>Pa.default.attribute({attribute:"id",operator:"=",value:e.value,quoteMark:'"'})},Ia=(0,Pa.default)((e=>e.map((e=>function(e){let t=e.filter((e=>"pseudo"!==e.type||e.nodes.length>0||e.value.startsWith("::")||[":before",":after",":first-line",":first-letter"].includes(e.value))).reverse(),r=new Set(["tag","class","id","attribute"]),n=t.findIndex((e=>r.has(e.type)));if(-1===n)return t.reverse().join("").trim();let i=t[n],o=ja[i.type]?ja[i.type](i):i;t=t.slice(0,n);let s=t.findIndex((e=>"combinator"===e.type&&">"===e.value));return-1!==s&&(t.splice(0,s),t.unshift(Pa.default.universal())),[o,...t.reverse()].join("").trim()}(e.split((e=>"combinator"===e.type&&" "===e.value)).pop()))))),Da=new Map}));function Ba(){function e(t){let r=null;t.each((e=>{if(!Ma.has(e.type))return void(r=null);if(null===r)return void(r=e);let t=Ra[e.type];"atrule"===e.type&&"font-face"===e.name?r=e:t.every((t=>(e[t]??"").replace(/\s+/g," ")===(r[t]??"").replace(/\s+/g," ")))?(e.nodes&&r.append(e.nodes),e.remove()):r=e})),t.each((t=>{"atrule"===t.type&&e(t)}))}return t=>{e(t)}}var Ra,Ma,Ua=p((()=>{g(),Ra={atrule:["name","params"],rule:["selector"]},Ma=new Set(Object.keys(Ra))}));function za(e){let t=/^-?\d*.?\d+([\w%]+)?$/g.exec(e);return t?t[1]??La:null}var La,Fa=p((()=>{g(),La=Symbol("unitless-number")}));function Na(){return e=>{!function(e){if(!e.walkAtRules)return;let t=new Set;if(e.walkAtRules("apply",(e=>{t.add(e.parent)})),0!==t.size)for(let e of t){let t=[],r=[];for(let n of e.nodes)"atrule"===n.type&&"apply"===n.name?(r.length>0&&(t.push(r),r=[]),t.push([n])):r.push(n);if(r.length>0&&t.push(r),1!==t.length){for(let r of[...t].reverse()){let t=e.clone({nodes:[]});t.append(r),e.after(t)}e.remove()}}}(e)}}var Va=p((()=>{g()}));function Wa(t){return async function(n,i){let{tailwindDirectives:o,applyDirectives:s}=function(e){let t=new Set,r=new Set,n=new Set;if(e.walkAtRules((e=>{"apply"===e.name&&n.add(e),"import"===e.name&&('"tailwindcss/base"'===e.params||"'tailwindcss/base'"===e.params?(e.name="tailwind",e.params="base"):'"tailwindcss/components"'===e.params||"'tailwindcss/components'"===e.params?(e.name="tailwind",e.params="components"):'"tailwindcss/utilities"'===e.params||"'tailwindcss/utilities'"===e.params?(e.name="tailwind",e.params="utilities"):('"tailwindcss/screens"'===e.params||"'tailwindcss/screens'"===e.params||'"tailwindcss/variants"'===e.params||"'tailwindcss/variants'"===e.params)&&(e.name="tailwind",e.params="variants")),"tailwind"===e.name&&("screens"===e.params&&(e.params="variants"),t.add(e.params)),["layer","responsive","variants"].includes(e.name)&&(["responsive","variants"].includes(e.name)&&D.warn(`${e.name}-at-rule-deprecated`,[`The \`@${e.name}\` directive has been deprecated in Tailwind CSS v3.0.`,"Use `@layer utilities` or `@layer components` instead.","https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer"]),r.add(e))})),!t.has("base")||!t.has("components")||!t.has("utilities"))for(let e of r)if("layer"===e.name&&["base","components","utilities"].includes(e.params)){if(!t.has(e.params))throw e.error(`\`@layer ${e.params}\` is used but no matching \`@tailwind ${e.params}\` directive is present.`)}else if("responsive"===e.name){if(!t.has("utilities"))throw e.error("`@responsive` is used but `@tailwind utilities` is missing.")}else if("variants"===e.name&&!t.has("utilities"))throw e.error("`@variants` is used but `@tailwind utilities` is missing.");return{tailwindDirectives:t,applyDirectives:n}}(n);Na()(n,i);let a=t({tailwindDirectives:o,applyDirectives:s,registerDependency(e){i.messages.push({plugin:"tailwindcss",parent:i.opts.from,...e})},createContext:(e,t)=>Io(e,t,n)})(n,i);if("-"===a.tailwindConfig.separator)throw new Error("The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead.");(function(t){if(void 0===e.env.JEST_WORKER_ID&&W(t).length>0){let e=W(t).map((e=>_.yellow(e))).join(", ");D.warn("experimental-flags-enabled",[`You have enabled experimental features: ${e}`,"Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time."])}})(a.tailwindConfig),await function(e){return async t=>{let n={base:null,components:null,utilities:null,variants:null};if(t.walkAtRules((e=>{"tailwind"===e.name&&Object.keys(n).includes(e.params)&&(n[e.params]=e)})),Object.values(n).every((e=>null===e)))return t;let i=new Set([...e.candidates??[],yi]),o=new Set;Gs.DEBUG&&console.time("Reading changed files");let s=[];for(let t of e.changedContent){let r=Vs(e.tailwindConfig,t.extension),n=Ns(e,t.extension);s.push([t,{transformer:r,extractor:n}])}for(let e=0;e{Ws(n(t=e?await r.promises.readFile(e,"utf8"):t),s,i,o)})))}Gs.DEBUG&&console.timeEnd("Reading changed files");let a=e.classCache.size;Gs.DEBUG&&console.time("Generate rules"),Gs.DEBUG&&console.time("Sorting candidates");let l=new Set([...i].sort(((e,t)=>e===t?0:e{let t=e.raws.tailwind?.parentLayer;return"components"===t?null!==n.components:"utilities"!==t||null!==n.utilities}));n.variants?(n.variants.before(Os(h,n.variants.source,{layer:"variants"})),n.variants.remove()):h.length>0&&t.append(Os(h,t.source,{layer:"variants"})),t.source.end=t.source.end??t.source.start;let m=h.some((e=>"utilities"===e.raws.tailwind?.parentLayer));n.utilities&&0===p.size&&!m&&D.warn("content-problems",["No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.","https://tailwindcss.com/docs/content-configuration"]),Gs.DEBUG&&(console.log("Potential classes: ",i.size),console.log("Active contexts: ",mi.size)),e.changedContent=[],t.walkAtRules("layer",(e=>{Object.keys(n).includes(e.params)&&e.remove()}))}}(a)(n,i),Na()(n,i),sa(a)(n,i),ka(a)(n,i),function({tailwindConfig:{theme:e}}){return function(t){t.walkAtRules("screen",(t=>{let r=t.params,n=Kn(e.screens).find((({name:e})=>e===r));if(!n)throw t.error(`No \`${r}\` screen found.`);t.name="media",t.params=$n(n)}))}}(a)(n,i),function({tailwindConfig:e}){return t=>{let r=new Map,n=new Set;if(t.walkAtRules("defaults",(e=>{if(e.nodes&&e.nodes.length>0)return void n.add(e);let t=e.params;r.has(t)||r.set(t,new Set),r.get(t).add(e.parent),e.remove()})),V(e,"optimizeUniversalDefaults"))for(let t of n){let n=new Map,i=r.get(t.params)??[];for(let e of i)for(let t of Ta(e.selector)){let e=t.includes(":-")||t.includes("::-")||t.includes(":has")?t:"__DEFAULT__",r=n.get(e)??new Set;n.set(e,r),r.add(t)}if(V(e,"optimizeUniversalDefaults")){if(0===n.size){t.remove();continue}for(let[,e]of n){let r=Wt.rule({source:t.source});r.selectors=[...e],r.append(t.nodes.map((e=>e.clone()))),t.before(r)}}t.remove()}else if(n.size){let e=Wt.rule({selectors:["*","::before","::after"]});for(let t of n)e.append(t.nodes),e.parent||t.before(e),e.source||(e.source=t.source),t.remove();let t=e.clone({selectors:["::backdrop"]});e.after(t)}}}(a)(n,i),Ba()(n,i),(e=>{e.walkRules((e=>{let t=new Map,r=new Set([]),n=new Map;e.walkDecls((i=>{if(i.parent===e){if(t.has(i.prop)){if(t.get(i.prop).value===i.value)return r.add(t.get(i.prop)),void t.set(i.prop,i);n.has(i.prop)||n.set(i.prop,new Set),n.get(i.prop).add(t.get(i.prop)),n.get(i.prop).add(i)}t.set(i.prop,i)}}));for(let e of r)e.remove();for(let e of n.values()){let t=new Map;for(let r of e){let e=za(r.value);null!==e&&(t.has(e)||t.set(e,new Set),t.get(e).add(r))}for(let e of t.values()){let t=Array.from(e).slice(0,-1);for(let e of t)e.remove()}}}))})(n)}}var qa=p((()=>{g(),As(),Js(),ca(),Ea(),_a(),$a(),Ua(),Fa(),Va(),No(),Y()})),Ga=p((()=>{g(),y(),Rt()})),Ya=f(((e,t)=>{g(),Cs(),qa(),vi(),Ga(),t.exports=function(e){return{postcssPlugin:"tailwindcss",plugins:[pi.DEBUG&&function(e){return console.log("\n"),console.time("JIT TOTAL"),e},async function(t,n){e=function(e,t){let n=null,i=null;return e.walkAtRules("config",(e=>{if(i=e.source?.input.file??t.opts.from??null,null===i)throw e.error("The `@config` directive cannot be used without setting `from` in your PostCSS config.");if(n)throw e.error("Only one `@config` directive is allowed per file.");let o=e.params.match(/(['"])(.*?)\1/);if(!o)throw e.error("A path is required when using the `@config` directive.");let s=o[2];if(Bt.isAbsolute(s))throw e.error("The `@config` directive cannot be used with an absolute path.");if(n=Bt.resolve(Bt.dirname(i),s),!r.existsSync(n))throw e.error(`The config file at "${s}" does not exist. Make sure the path is correct and the file exists.`);e.remove()})),n||null}(t,n)??e;let i=ws(e);if("document"!==t.type)await Wa(i)(t,n);else{let e=t.nodes.filter((e=>"root"===e.type));for(let t of e)"root"===t.type&&await Wa(i)(t,n)}},pi.DEBUG&&function(e){return console.timeEnd("JIT TOTAL"),console.log("\n"),e}].filter(Boolean)}},t.exports.postcss=!0})),Ha=f(((e,t)=>{g(),t.exports=Ya()})),Qa=f(((e,t)=>{g(),t.exports=()=>["and_chr 114","and_uc 15.5","chrome 114","chrome 113","chrome 109","edge 114","firefox 114","ios_saf 16.5","ios_saf 16.4","ios_saf 16.3","ios_saf 16.1","opera 99","safari 16.5","samsung 21"]})),Ja={};function Xa(){return{status:"cr",title:"CSS Feature Queries",stats:{ie:{6:"n",7:"n",8:"n",9:"n",10:"n",11:"n",5.5:"n"},edge:{12:"y",13:"y",14:"y",15:"y",16:"y",17:"y",18:"y",79:"y",80:"y",81:"y",83:"y",84:"y",85:"y",86:"y",87:"y",88:"y",89:"y",90:"y",91:"y",92:"y",93:"y",94:"y",95:"y",96:"y",97:"y",98:"y",99:"y",100:"y",101:"y",102:"y",103:"y",104:"y",105:"y",106:"y",107:"y",108:"y",109:"y",110:"y",111:"y",112:"y",113:"y",114:"y"},firefox:{2:"n",3:"n",4:"n",5:"n",6:"n",7:"n",8:"n",9:"n",10:"n",11:"n",12:"n",13:"n",14:"n",15:"n",16:"n",17:"n",18:"n",19:"n",20:"n",21:"n",22:"y",23:"y",24:"y",25:"y",26:"y",27:"y",28:"y",29:"y",30:"y",31:"y",32:"y",33:"y",34:"y",35:"y",36:"y",37:"y",38:"y",39:"y",40:"y",41:"y",42:"y",43:"y",44:"y",45:"y",46:"y",47:"y",48:"y",49:"y",50:"y",51:"y",52:"y",53:"y",54:"y",55:"y",56:"y",57:"y",58:"y",59:"y",60:"y",61:"y",62:"y",63:"y",64:"y",65:"y",66:"y",67:"y",68:"y",69:"y",70:"y",71:"y",72:"y",73:"y",74:"y",75:"y",76:"y",77:"y",78:"y",79:"y",80:"y",81:"y",82:"y",83:"y",84:"y",85:"y",86:"y",87:"y",88:"y",89:"y",90:"y",91:"y",92:"y",93:"y",94:"y",95:"y",96:"y",97:"y",98:"y",99:"y",100:"y",101:"y",102:"y",103:"y",104:"y",105:"y",106:"y",107:"y",108:"y",109:"y",110:"y",111:"y",112:"y",113:"y",114:"y",115:"y",116:"y",117:"y",3.5:"n",3.6:"n"},chrome:{4:"n",5:"n",6:"n",7:"n",8:"n",9:"n",10:"n",11:"n",12:"n",13:"n",14:"n",15:"n",16:"n",17:"n",18:"n",19:"n",20:"n",21:"n",22:"n",23:"n",24:"n",25:"n",26:"n",27:"n",28:"y",29:"y",30:"y",31:"y",32:"y",33:"y",34:"y",35:"y",36:"y",37:"y",38:"y",39:"y",40:"y",41:"y",42:"y",43:"y",44:"y",45:"y",46:"y",47:"y",48:"y",49:"y",50:"y",51:"y",52:"y",53:"y",54:"y",55:"y",56:"y",57:"y",58:"y",59:"y",60:"y",61:"y",62:"y",63:"y",64:"y",65:"y",66:"y",67:"y",68:"y",69:"y",70:"y",71:"y",72:"y",73:"y",74:"y",75:"y",76:"y",77:"y",78:"y",79:"y",80:"y",81:"y",83:"y",84:"y",85:"y",86:"y",87:"y",88:"y",89:"y",90:"y",91:"y",92:"y",93:"y",94:"y",95:"y",96:"y",97:"y",98:"y",99:"y",100:"y",101:"y",102:"y",103:"y",104:"y",105:"y",106:"y",107:"y",108:"y",109:"y",110:"y",111:"y",112:"y",113:"y",114:"y",115:"y",116:"y",117:"y"},safari:{4:"n",5:"n",6:"n",7:"n",8:"n",9:"y",10:"y",11:"y",12:"y",13:"y",14:"y",15:"y",17:"y",9.1:"y",10.1:"y",11.1:"y",12.1:"y",13.1:"y",14.1:"y",15.1:"y","15.2-15.3":"y",15.4:"y",15.5:"y",15.6:"y","16.0":"y",16.1:"y",16.2:"y",16.3:"y",16.4:"y",16.5:"y",16.6:"y",TP:"y",3.1:"n",3.2:"n",5.1:"n",6.1:"n",7.1:"n"},opera:{9:"n",11:"n",12:"n",15:"y",16:"y",17:"y",18:"y",19:"y",20:"y",21:"y",22:"y",23:"y",24:"y",25:"y",26:"y",27:"y",28:"y",29:"y",30:"y",31:"y",32:"y",33:"y",34:"y",35:"y",36:"y",37:"y",38:"y",39:"y",40:"y",41:"y",42:"y",43:"y",44:"y",45:"y",46:"y",47:"y",48:"y",49:"y",50:"y",51:"y",52:"y",53:"y",54:"y",55:"y",56:"y",57:"y",58:"y",60:"y",62:"y",63:"y",64:"y",65:"y",66:"y",67:"y",68:"y",69:"y",70:"y",71:"y",72:"y",73:"y",74:"y",75:"y",76:"y",77:"y",78:"y",79:"y",80:"y",81:"y",82:"y",83:"y",84:"y",85:"y",86:"y",87:"y",88:"y",89:"y",90:"y",91:"y",92:"y",93:"y",94:"y",95:"y",96:"y",97:"y",98:"y",99:"y",100:"y",12.1:"y","9.5-9.6":"n","10.0-10.1":"n",10.5:"n",10.6:"n",11.1:"n",11.5:"n",11.6:"n"},ios_saf:{8:"n",17:"y","9.0-9.2":"y",9.3:"y","10.0-10.2":"y",10.3:"y","11.0-11.2":"y","11.3-11.4":"y","12.0-12.1":"y","12.2-12.5":"y","13.0-13.1":"y",13.2:"y",13.3:"y","13.4-13.7":"y","14.0-14.4":"y","14.5-14.8":"y","15.0-15.1":"y","15.2-15.3":"y",15.4:"y",15.5:"y",15.6:"y","16.0":"y",16.1:"y",16.2:"y",16.3:"y",16.4:"y",16.5:"y",16.6:"y",3.2:"n","4.0-4.1":"n","4.2-4.3":"n","5.0-5.1":"n","6.0-6.1":"n","7.0-7.1":"n","8.1-8.4":"n"},op_mini:{all:"y"},android:{3:"n",4:"n",114:"y",4.4:"y","4.4.3-4.4.4":"y",2.1:"n",2.2:"n",2.3:"n",4.1:"n","4.2-4.3":"n"},bb:{7:"n",10:"n"},op_mob:{10:"n",11:"n",12:"n",73:"y",11.1:"n",11.5:"n",12.1:"n"},and_chr:{114:"y"},and_ff:{115:"y"},ie_mob:{10:"n",11:"n"},and_uc:{15.5:"y"},samsung:{4:"y",20:"y",21:"y","5.0-5.4":"y","6.2-6.4":"y","7.2-7.4":"y",8.2:"y",9.2:"y",10.1:"y","11.1-11.2":"y","12.0":"y","13.0":"y","14.0":"y","15.0":"y","16.0":"y","17.0":"y","18.0":"y","19.0":"y"},and_qq:{13.1:"y"},baidu:{13.18:"y"},kaios:{2.5:"y","3.0-3.1":"y"}}}}h(Ja,{agents:()=>Ka,feature:()=>Xa});var Ka,Za=p((()=>{g(),Ka={ie:{prefix:"ms"},edge:{prefix:"webkit",prefix_exceptions:{12:"ms",13:"ms",14:"ms",15:"ms",16:"ms",17:"ms",18:"ms"}},firefox:{prefix:"moz"},chrome:{prefix:"webkit"},safari:{prefix:"webkit"},opera:{prefix:"webkit",prefix_exceptions:{9:"o",11:"o",12:"o","9.5-9.6":"o","10.0-10.1":"o",10.5:"o",10.6:"o",11.1:"o",11.5:"o",11.6:"o",12.1:"o"}},ios_saf:{prefix:"webkit"},op_mini:{prefix:"o"},android:{prefix:"webkit"},bb:{prefix:"webkit"},op_mob:{prefix:"o",prefix_exceptions:{73:"webkit"}},and_chr:{prefix:"webkit"},and_ff:{prefix:"moz"},ie_mob:{prefix:"ms"},and_uc:{prefix:"webkit",prefix_exceptions:{15.5:"webkit"}},samsung:{prefix:"webkit"},and_qq:{prefix:"webkit"},baidu:{prefix:"webkit"},kaios:{prefix:"moz"}}})),el=f((()=>{g()})),tl=f(((e,t)=>{g();var{list:r}=xr();t.exports.error=function(e){let t=new Error(e);throw t.autoprefixer=!0,t},t.exports.uniq=function(e){return[...new Set(e)]},t.exports.removeNote=function(e){return e.includes(" ")?e.split(" ")[0]:e},t.exports.escapeRegexp=function(e){return e.replace(/[$()*+-.?[\\\]^{|}]/g,"\\$&")},t.exports.regexp=function(e,t=!0){return t&&(e=this.escapeRegexp(e)),new RegExp(`(^|[\\s,(])(${e}($|[\\s(,]))`,"gi")},t.exports.editList=function(e,t){let n=r.comma(e),i=t(n,[]);if(n===i)return e;let o=e.match(/,\s*/);return o=o?o[0]:", ",i.join(o)},t.exports.splitSelector=function(e){return r.comma(e).map((e=>r.space(e).map((e=>e.split(/(?=\.|#)/g)))))}})),rl=f(((e,t)=>{g();var r=Qa(),n=(Za(),Ja).agents,i=tl();t.exports=class{static prefixes(){if(this.prefixesCache)return this.prefixesCache;this.prefixesCache=[];for(let e in n)this.prefixesCache.push(`-${n[e].prefix}-`);return this.prefixesCache=i.uniq(this.prefixesCache).sort(((e,t)=>t.length-e.length)),this.prefixesCache}static withPrefix(e){return this.prefixesRegexp||(this.prefixesRegexp=new RegExp(this.prefixes().join("|"))),this.prefixesRegexp.test(e)}constructor(e,t,r,n){this.data=e,this.options=r||{},this.browserslistOpts=n||{},this.selected=this.parse(t)}parse(e){let t={};for(let e in this.browserslistOpts)t[e]=this.browserslistOpts[e];return t.path=this.options.from,r(e,t)}prefix(e){let[t,r]=e.split(" "),n=this.data[t],i=n.prefix_exceptions&&n.prefix_exceptions[r];return i||(i=n.prefix),`-${i}-`}isSelected(e){return this.selected.includes(e)}}})),nl=f(((e,t)=>{g(),t.exports={prefix(e){let t=e.match(/^(-\w+-)/);return t?t[0]:""},unprefixed:e=>e.replace(/^-\w+-/,"")}})),il=f(((e,t)=>{g();var r=rl(),n=nl(),i=tl();function o(e,t){let r=new e.constructor;for(let n of Object.keys(e||{})){let i=e[n];"parent"===n&&"object"==typeof i?t&&(r[n]=t):"source"===n||null===n?r[n]=i:Array.isArray(i)?r[n]=i.map((e=>o(e,r))):"_autoprefixerPrefix"!==n&&"_autoprefixerValues"!==n&&"proxyCache"!==n&&("object"==typeof i&&null!==i&&(i=o(i,r)),r[n]=i)}return r}var s=class{static hack(e){return this.hacks||(this.hacks={}),e.names.map((t=>(this.hacks[t]=e,this.hacks[t])))}static load(e,t,r){let n=this.hacks&&this.hacks[e];return n?new n(e,t,r):new this(e,t,r)}static clone(e,t){let r=o(e);for(let e in t)r[e]=t[e];return r}constructor(e,t,r){this.prefixes=t,this.name=e,this.all=r}parentPrefix(e){let t;return t=void 0!==e._autoprefixerPrefix?e._autoprefixerPrefix:"decl"===e.type&&"-"===e.prop[0]?n.prefix(e.prop):"root"!==e.type&&("rule"===e.type&&e.selector.includes(":-")&&/:(-\w+-)/.test(e.selector)?e.selector.match(/:(-\w+-)/)[1]:"atrule"===e.type&&"-"===e.name[0]?n.prefix(e.name):this.parentPrefix(e.parent)),r.prefixes().includes(t)||(t=!1),e._autoprefixerPrefix=t,e._autoprefixerPrefix}process(e,t){if(!this.check(e))return;let r=this.parentPrefix(e),n=this.prefixes.filter((e=>!r||r===i.removeNote(e))),o=[];for(let r of n)this.add(e,r,o.concat([r]),t)&&o.push(r);return o}clone(e,t){return s.clone(e,t)}};t.exports=s})),ol=f(((e,t)=>{g();var r=il(),n=rl(),i=tl();t.exports=class extends r{check(){return!0}prefixed(e,t){return t+e}normalize(e){return e}otherPrefixes(e,t){for(let r of n.prefixes())if(r!==t&&e.includes(r))return!0;return!1}set(e,t){return e.prop=this.prefixed(e.prop,t),e}needCascade(e){return e._autoprefixerCascade||(e._autoprefixerCascade=!1!==this.all.options.cascade&&e.raw("before").includes("\n")),e._autoprefixerCascade}maxPrefixed(e,t){if(t._autoprefixerMax)return t._autoprefixerMax;let r=0;for(let t of e)t=i.removeNote(t),t.length>r&&(r=t.length);return t._autoprefixerMax=r,t._autoprefixerMax}calcBefore(e,t,r=""){let n=this.maxPrefixed(e,t)-i.removeNote(r).length,o=t.raw("before");return n>0&&(o+=Array(n).fill(" ").join("")),o}restoreBefore(e){let t=e.raw("before").split("\n"),r=t[t.length-1];this.all.group(e).up((e=>{let t=e.raw("before").split("\n"),n=t[t.length-1];n.lengthe.prop===n.prop&&e.value===n.value)))return this.needCascade(e)&&(n.raws.before=this.calcBefore(r,e,t)),e.parent.insertBefore(e,n)}isAlready(e,t){let r=this.all.group(e).up((e=>e.prop===t));return r||(r=this.all.group(e).down((e=>e.prop===t))),r}add(e,t,r,n){let i=this.prefixed(e.prop,t);if(!this.isAlready(e,i)&&!this.otherPrefixes(e.value,t))return this.insert(e,t,r,n)}process(e,t){if(!this.needCascade(e))return void super.process(e,t);let r=super.process(e,t);!r||!r.length||(this.restoreBefore(e),e.raws.before=this.calcBefore(r,e))}old(e,t){return[this.prefixed(e,t)]}}})),sl=f(((e,t)=>{g(),t.exports=function e(t){return{mul:r=>new e(t*r),div:r=>new e(t/r),simplify:()=>new e(t),toString:()=>t.toString()}}})),al=f(((e,t)=>{g();var r=sl(),n=il(),i=tl(),o=/(min|max)-resolution\s*:\s*\d*\.?\d+(dppx|dpcm|dpi|x)/gi,s=/(min|max)-resolution(\s*:\s*)(\d*\.?\d+)(dppx|dpcm|dpi|x)/i;t.exports=class extends n{prefixName(e,t){return"-moz-"===e?t+"--moz-device-pixel-ratio":e+t+"-device-pixel-ratio"}prefixQuery(e,t,n,i,o){return i=new r(i),"dpi"===o?i=i.div(96):"dpcm"===o&&(i=i.mul(2.54).div(96)),i=i.simplify(),"-o-"===e&&(i=i.n+"/"+i.d),this.prefixName(e,t)+n+i}clean(e){if(!this.bad){this.bad=[];for(let e of this.prefixes)this.bad.push(this.prefixName(e,"min")),this.bad.push(this.prefixName(e,"max"))}e.params=i.editList(e.params,(e=>e.filter((e=>this.bad.every((t=>!e.includes(t)))))))}process(e){let t=this.parentPrefix(e),r=t?[t]:this.prefixes;e.params=i.editList(e.params,((e,t)=>{for(let n of e)if(n.includes("min-resolution")||n.includes("max-resolution")){for(let e of r){let r=n.replace(o,(t=>{let r=t.match(s);return this.prefixQuery(e,r[1],r[2],r[3],r[4])}));t.push(r)}t.push(n)}else t.push(n);return i.uniq(t)}))}}})),ll=f(((e,t)=>{g();var r="(".charCodeAt(0),n=")".charCodeAt(0),i="'".charCodeAt(0),o='"'.charCodeAt(0),s="\\".charCodeAt(0),a="/".charCodeAt(0),l=",".charCodeAt(0),c=":".charCodeAt(0),u="*".charCodeAt(0),d="u".charCodeAt(0),p="U".charCodeAt(0),f="+".charCodeAt(0),h=/^[a-f0-9?-]+$/i;t.exports=function(e){for(var t,m,g,y,b,v,w,x,k,S=[],C=e,A=0,O=C.charCodeAt(A),E=C.length,_=[{nodes:S}],T=0,P="",j="",I="";A{g(),t.exports=function e(t,r,n){var i,o,s,a;for(i=0,o=t.length;i{function r(e,t){var r,i,o=e.type,s=e.value;return t&&void 0!==(i=t(e))?i:"word"===o||"space"===o?s:"string"===o?(r=e.quote||"")+s+(e.unclosed?"":r):"comment"===o?"/*"+s+(e.unclosed?"":"*/"):"div"===o?(e.before||"")+s+(e.after||""):Array.isArray(e.nodes)?(r=n(e.nodes,t),"function"!==o?r:s+"("+(e.before||"")+r+(e.after||"")+(e.unclosed?"":")")):s}function n(e,t){var n,i;if(Array.isArray(e)){for(n="",i=e.length-1;~i;i-=1)n=r(e[i],t)+n;return n}return r(e,t)}g(),t.exports=n})),dl=f(((e,t)=>{g();var r="-".charCodeAt(0),n="+".charCodeAt(0),i=".".charCodeAt(0),o="e".charCodeAt(0),s="E".charCodeAt(0);t.exports=function(e){var t,a,l,c=0,u=e.length;if(0===u||!function(e){var t,o=e.charCodeAt(0);if(o===n||o===r){if((t=e.charCodeAt(1))>=48&&t<=57)return!0;var s=e.charCodeAt(2);return t===i&&s>=48&&s<=57}return o===i?(t=e.charCodeAt(1))>=48&&t<=57:o>=48&&o<=57}(e))return!1;for(((t=e.charCodeAt(c))===n||t===r)&&c++;c57);)c+=1;if(t=e.charCodeAt(c),a=e.charCodeAt(c+1),t===i&&a>=48&&a<=57)for(c+=2;c57);)c+=1;if(t=e.charCodeAt(c),a=e.charCodeAt(c+1),l=e.charCodeAt(c+2),(t===o||t===s)&&(a>=48&&a<=57||(a===n||a===r)&&l>=48&&l<=57))for(c+=a===n||a===r?3:2;c57);)c+=1;return{number:e.slice(0,c),unit:e.slice(c)}}})),pl=f(((e,t)=>{g();var r=ll(),n=cl(),i=ul();function o(e){return this instanceof o?(this.nodes=r(e),this):new o(e)}o.prototype.toString=function(){return Array.isArray(this.nodes)?i(this.nodes):""},o.prototype.walk=function(e,t){return n(this.nodes,e,t),this},o.unit=dl(),o.walk=n,o.stringify=i,t.exports=o})),fl=f(((e,t)=>{g();var{list:r}=xr(),n=pl(),i=rl(),o=nl();t.exports=class{constructor(e){this.props=["transition","transition-property"],this.prefixes=e}add(e,t){let r,n,i=this.prefixes.add[e.prop],o=this.ruleVendorPrefixes(e),s=o||i&&i.prefixes||[],a=this.parse(e.value),l=a.map((e=>this.findProp(e))),c=[];if(l.some((e=>"-"===e[0])))return;for(let e of a){if(n=this.findProp(e),"-"===n[0])continue;let t=this.prefixes.add[n];if(t&&t.prefixes)for(r of t.prefixes){if(o&&!o.some((e=>r.includes(e))))continue;let t=this.prefixes.prefixed(n,r);"-ms-transform"!==t&&!l.includes(t)&&(this.disabled(n,r)||c.push(this.clone(n,t,e)))}}a=a.concat(c);let u=this.stringify(a),d=this.stringify(this.cleanFromUnprefixed(a,"-webkit-"));if(s.includes("-webkit-")&&this.cloneBefore(e,`-webkit-${e.prop}`,d),this.cloneBefore(e,e.prop,d),s.includes("-o-")){let t=this.stringify(this.cleanFromUnprefixed(a,"-o-"));this.cloneBefore(e,`-o-${e.prop}`,t)}for(r of s)if("-webkit-"!==r&&"-o-"!==r){let t=this.stringify(this.cleanOtherPrefixes(a,r));this.cloneBefore(e,r+e.prop,t)}u!==e.value&&!this.already(e,e.prop,u)&&(this.checkForWarning(t,e),e.cloneBefore(),e.value=u)}findProp(e){let t=e[0].value;if(/^\d/.test(t))for(let[t,r]of e.entries())if(0!==t&&"word"===r.type)return r.value;return t}already(e,t,r){return e.parent.some((e=>e.prop===t&&e.value===r))}cloneBefore(e,t,r){this.already(e,t,r)||e.cloneBefore({prop:t,value:r})}checkForWarning(e,t){if("transition-property"!==t.prop)return;let n=!1,i=!1;t.parent.each((e=>{if("decl"!==e.type||0!==e.prop.indexOf("transition-"))return;let t=r.comma(e.value);if("transition-property"!==e.prop)return i=i||t.length>1,!1;t.forEach((e=>{let t=this.prefixes.add[e];t&&t.prefixes&&t.prefixes.length>0&&(n=!0)}))})),n&&i&&t.warn(e,"Replace transition-property to transition, because Autoprefixer could not support any cases of transition-property and other transition-*")}remove(e){let t=this.parse(e.value);t=t.filter((e=>{let t=this.prefixes.remove[this.findProp(e)];return!t||!t.remove}));let r=this.stringify(t);if(e.value===r)return;if(0===t.length)return void e.remove();let n=e.parent.some((t=>t.prop===e.prop&&t.value===r)),i=e.parent.some((t=>t!==e&&t.prop===e.prop&&t.value.length>r.length));n||i?e.remove():e.value=r}parse(e){let t=n(e),r=[],i=[];for(let e of t.nodes)i.push(e),"div"===e.type&&","===e.value&&(r.push(i),i=[]);return r.push(i),r.filter((e=>e.length>0))}stringify(e){if(0===e.length)return"";let t=[];for(let r of e)"div"!==r[r.length-1].type&&r.push(this.div(e)),t=t.concat(r);return"div"===t[0].type&&(t=t.slice(1)),"div"===t[t.length-1].type&&(t=t.slice(0,-1)),n.stringify({nodes:t})}clone(e,t,r){let n=[],i=!1;for(let o of r)i||"word"!==o.type||o.value!==e?n.push(o):(n.push({type:"word",value:t}),i=!0);return n}div(e){for(let t of e)for(let e of t)if("div"===e.type&&","===e.value)return e;return{type:"div",value:",",after:" "}}cleanOtherPrefixes(e,t){return e.filter((e=>{let r=o.prefix(this.findProp(e));return""===r||r===t}))}cleanFromUnprefixed(e,t){let r=e.map((e=>this.findProp(e))).filter((e=>e.slice(0,t.length)===t)).map((e=>this.prefixes.unprefixed(e))),n=[];for(let i of e){let e=this.findProp(i),s=o.prefix(e);!r.includes(e)&&(s===t||""===s)&&n.push(i)}return n}disabled(e,t){if(e.includes("flex")||["order","justify-content","align-self","align-content"].includes(e)){if(!1===this.prefixes.options.flexbox)return!0;if("no-2009"===this.prefixes.options.flexbox)return t.includes("2009")}}ruleVendorPrefixes(e){let{parent:t}=e;if("rule"!==t.type)return!1;if(!t.selector.includes(":-"))return!1;let r=i.prefixes().filter((e=>t.selector.includes(":"+e)));return r.length>0&&r}}})),hl=f(((e,t)=>{g();var r=tl();t.exports=class{constructor(e,t,n,i){this.unprefixed=e,this.prefixed=t,this.string=n||t,this.regexp=i||r.regexp(t)}check(e){return!!e.includes(this.string)&&!!e.match(this.regexp)}}})),ml=f(((e,t)=>{g();var r=il(),n=hl(),i=nl(),o=tl();t.exports=class extends r{static save(e,t){let r=t.prop,n=[];for(let o in t._autoprefixerValues){let s=t._autoprefixerValues[o];if(s===t.value)continue;let a,l=i.prefix(r);if("-pie-"===l)continue;if(l===o){a=t.value=s,n.push(a);continue}let c=e.prefixed(r,o),u=t.parent;if(!u.every((e=>e.prop!==c))){n.push(a);continue}let d=s.replace(/\s+/," ");if(u.some((e=>e.prop===t.prop&&e.value.replace(/\s+/," ")===d))){n.push(a);continue}let p=this.clone(t,{value:s});a=t.parent.insertBefore(t,p),n.push(a)}return n}check(e){let t=e.value;return!!t.includes(this.name)&&!!t.match(this.regexp())}regexp(){return this.regexpCache||(this.regexpCache=o.regexp(this.name))}replace(e,t){return e.replace(this.regexp(),`$1${t}$2`)}value(e){return e.raws.value&&e.raws.value.value===e.value?e.raws.value.raw:e.value}add(e,t){e._autoprefixerValues||(e._autoprefixerValues={});let r,n=e._autoprefixerValues[t]||this.value(e);do{if(r=n,n=this.replace(n,t),!1===n)return}while(n!==r);e._autoprefixerValues[t]=n}old(e){return new n(this.name,e+this.name)}}})),gl=f(((e,t)=>{g(),t.exports={}})),yl=f(((t,r)=>{g();var n=pl(),i=ml(),o=gl().insertAreas,s=/(^|[^-])linear-gradient\(\s*(top|left|right|bottom)/i,a=/(^|[^-])radial-gradient\(\s*\d+(\w*|%)\s+\d+(\w*|%)\s*,/i,l=/(!\s*)?autoprefixer:\s*ignore\s+next/i,c=/(!\s*)?autoprefixer\s*grid:\s*(on|off|(no-)?autoplace)/i,u=["width","height","min-width","max-width","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size"];function d(e){return e.parent.some((e=>"grid-template"===e.prop||"grid-template-areas"===e.prop))}r.exports=class{constructor(e){this.prefixes=e}add(e,t){let r=this.prefixes.add["@resolution"],l=this.prefixes.add["@keyframes"],c=this.prefixes.add["@viewport"],p=this.prefixes.add["@supports"];function f(e){return e.parent.nodes.some((e=>{if("decl"!==e.type)return!1;let t="display"===e.prop&&/(inline-)?grid/.test(e.value),r=e.prop.startsWith("grid-template"),n=/^grid-([A-z]+-)?gap/.test(e.prop);return t||r||n}))}e.walkAtRules((e=>{if("keyframes"===e.name){if(!this.disabled(e,t))return l&&l.process(e)}else if("viewport"===e.name){if(!this.disabled(e,t))return c&&c.process(e)}else if("supports"===e.name){if(!1!==this.prefixes.options.supports&&!this.disabled(e,t))return p.process(e)}else if("media"===e.name&&e.params.includes("-resolution")&&!this.disabled(e,t))return r&&r.process(e)})),e.walkRules((e=>{if(!this.disabled(e,t))return this.prefixes.add.selectors.map((r=>r.process(e,t)))}));let h=this.gridStatus(e,t)&&this.prefixes.add["grid-area"]&&this.prefixes.add["grid-area"].prefixes;return e.walkDecls((e=>{if(this.disabledDecl(e,t))return;let r,i=e.parent,o=e.prop,l=e.value;if("grid-row-span"!==o)if("grid-column-span"!==o)if("display"!==o||"box"!==l){if("text-emphasis-position"===o)("under"===l||"over"===l)&&t.warn("You should use 2 values for text-emphasis-position For example, `under left` instead of just `under`.",{node:e});else if(/^(align|justify|place)-(items|content)$/.test(o)&&function(e){return e.parent.some((e=>"display"===e.prop&&/(inline-)?flex/.test(e.value)))}(e))("start"===l||"end"===l)&&t.warn(`${l} value has mixed support, consider using flex-${l} instead`,{node:e});else if("text-decoration-skip"===o&&"ink"===l)t.warn("Replace text-decoration-skip: ink to text-decoration-skip-ink: auto, because spec had been changed",{node:e});else{if(h&&this.gridStatus(e,t))if("subgrid"===e.value&&t.warn("IE does not support subgrid",{node:e}),/^(align|justify|place)-items$/.test(o)&&f(e)){let r=o.replace("-items","-self");t.warn(`IE does not support ${o} on grid containers. Try using ${r} on child elements instead: ${e.parent.selector} > * { ${r}: ${e.value} }`,{node:e})}else if(/^(align|justify|place)-content$/.test(o)&&f(e))t.warn(`IE does not support ${e.prop} on grid containers`,{node:e});else{if("display"===o&&"contents"===e.value)return void t.warn("Please do not use display: contents; if you have grid setting enabled",{node:e});if("grid-gap"===e.prop){let r=this.gridStatus(e,t);"autoplace"!==r||function(e){let t=e.parent.some((e=>"grid-template-rows"===e.prop)),r=e.parent.some((e=>"grid-template-columns"===e.prop));return t&&r}(e)||d(e)?(!0===r||"no-autoplace"===r)&&!d(e)&&t.warn("grid-gap only works if grid-template(-areas) is being used",{node:e}):t.warn("grid-gap only works if grid-template(-areas) is being used or both rows and columns have been declared and cells have not been manually placed inside the explicit grid",{node:e})}else{if("grid-auto-columns"===o)return void t.warn("grid-auto-columns is not supported by IE",{node:e});if("grid-auto-rows"===o)return void t.warn("grid-auto-rows is not supported by IE",{node:e});if("grid-auto-flow"===o){let r=i.some((e=>"grid-template-rows"===e.prop)),n=i.some((e=>"grid-template-columns"===e.prop));return void(d(e)?t.warn("grid-auto-flow is not supported by IE",{node:e}):l.includes("dense")?t.warn("grid-auto-flow: dense is not supported by IE",{node:e}):!r&&!n&&t.warn("grid-auto-flow works only if grid-template-rows and grid-template-columns are present in the same rule",{node:e}))}if(l.includes("auto-fit"))return void t.warn("auto-fit value is not supported by IE",{node:e,word:"auto-fit"});if(l.includes("auto-fill"))return void t.warn("auto-fill value is not supported by IE",{node:e,word:"auto-fill"});o.startsWith("grid-template")&&l.includes("[")&&t.warn("Autoprefixer currently does not support line names. Try using grid-template-areas instead.",{node:e,word:"["})}}if(l.includes("radial-gradient"))if(a.test(e.value))t.warn("Gradient has outdated direction syntax. New syntax is like `closest-side at 0 0` instead of `0 0, closest-side`.",{node:e});else{let r=n(l);for(let n of r.nodes)if("function"===n.type&&"radial-gradient"===n.value)for(let r of n.nodes)"word"===r.type&&("cover"===r.value?t.warn("Gradient has outdated direction syntax. Replace `cover` to `farthest-corner`.",{node:e}):"contain"===r.value&&t.warn("Gradient has outdated direction syntax. Replace `contain` to `closest-side`.",{node:e}))}l.includes("linear-gradient")&&s.test(l)&&t.warn("Gradient has outdated direction syntax. New syntax is like `to left` instead of `right`.",{node:e})}if(u.includes(e.prop)&&(e.value.includes("-fill-available")||(e.value.includes("fill-available")?t.warn("Replace fill-available to stretch, because spec had been changed",{node:e}):e.value.includes("fill")&&n(l).nodes.some((e=>"word"===e.type&&"fill"===e.value))&&t.warn("Replace fill to stretch, because spec had been changed",{node:e}))),"transition"===e.prop||"transition-property"===e.prop)return this.prefixes.transition.add(e,t);if("align-self"===e.prop){if("grid"!==this.displayType(e)&&!1!==this.prefixes.options.flexbox&&(r=this.prefixes.add["align-self"],r&&r.prefixes&&r.process(e)),!1!==this.gridStatus(e,t)&&(r=this.prefixes.add["grid-row-align"],r&&r.prefixes))return r.process(e,t)}else if("justify-self"===e.prop){if(!1!==this.gridStatus(e,t)&&(r=this.prefixes.add["grid-column-align"],r&&r.prefixes))return r.process(e,t)}else if("place-self"===e.prop){if(r=this.prefixes.add["place-self"],r&&r.prefixes&&!1!==this.gridStatus(e,t))return r.process(e,t)}else if(r=this.prefixes.add[e.prop],r&&r.prefixes)return r.process(e,t)}else t.warn("You should write display: flex by final spec instead of display: box",{node:e});else t.warn("grid-column-span is not part of final Grid Layout. Use grid-column.",{node:e});else t.warn("grid-row-span is not part of final Grid Layout. Use grid-row.",{node:e})})),this.gridStatus(e,t)&&o(e,this.disabled),e.walkDecls((e=>{if(this.disabledValue(e,t))return;let r=this.prefixes.unprefixed(e.prop),n=this.prefixes.values("add",r);if(Array.isArray(n))for(let r of n)r.process&&r.process(e,t);i.save(this.prefixes,e)}))}remove(e,t){let r=this.prefixes.remove["@resolution"];e.walkAtRules(((e,n)=>{this.prefixes.remove[`@${e.name}`]?this.disabled(e,t)||e.parent.removeChild(n):"media"===e.name&&e.params.includes("-resolution")&&r&&r.clean(e)}));for(let r of this.prefixes.remove.selectors)e.walkRules(((e,n)=>{r.check(e)&&(this.disabled(e,t)||e.parent.removeChild(n))}));return e.walkDecls(((e,r)=>{if(this.disabled(e,t))return;let n=e.parent,i=this.prefixes.unprefixed(e.prop);if(("transition"===e.prop||"transition-property"===e.prop)&&this.prefixes.transition.remove(e),this.prefixes.remove[e.prop]&&this.prefixes.remove[e.prop].remove){let t=this.prefixes.group(e).down((e=>this.prefixes.normalize(e.prop)===i));if("flex-flow"===i&&(t=!0),"-webkit-box-orient"===e.prop){let t={"flex-direction":!0,"flex-flow":!0};if(!e.parent.some((e=>t[e.prop])))return}if(t&&!this.withHackValue(e))return e.raw("before").includes("\n")&&this.reduceSpaces(e),void n.removeChild(r)}for(let t of this.prefixes.values("remove",i))if(t.check&&t.check(e.value)&&(i=t.unprefixed,this.prefixes.group(e).down((e=>e.value.includes(i)))))return void n.removeChild(r)}))}withHackValue(e){return"-webkit-background-clip"===e.prop&&"text"===e.value}disabledValue(e,t){return!!(!1===this.gridStatus(e,t)&&"decl"===e.type&&"display"===e.prop&&e.value.includes("grid")||!1===this.prefixes.options.flexbox&&"decl"===e.type&&"display"===e.prop&&e.value.includes("flex")||"decl"===e.type&&"content"===e.prop)||this.disabled(e,t)}disabledDecl(e,t){if(!1===this.gridStatus(e,t)&&"decl"===e.type&&(e.prop.includes("grid")||"justify-items"===e.prop))return!0;if(!1===this.prefixes.options.flexbox&&"decl"===e.type){let t=["order","justify-content","align-items","align-content"];if(e.prop.includes("flex")||t.includes(e.prop))return!0}return this.disabled(e,t)}disabled(e,t){if(!e)return!1;if(void 0!==e._autoprefixerDisabled)return e._autoprefixerDisabled;if(e.parent){let t=e.prev();if(t&&"comment"===t.type&&l.test(t.text))return e._autoprefixerDisabled=!0,e._autoprefixerSelfDisabled=!0,!0}let r=null;if(e.nodes){let n;e.each((e=>{"comment"===e.type&&/(!\s*)?autoprefixer:\s*(off|on)/i.test(e.text)&&(void 0!==n?t.warn("Second Autoprefixer control comment was ignored. Autoprefixer applies control comment to whole block, not to next rules.",{node:e}):n=/on/i.test(e.text))})),void 0!==n&&(r=!n)}if(!e.nodes||null===r)if(e.parent){let n=this.disabled(e.parent,t);r=!0!==e.parent._autoprefixerSelfDisabled&&n}else r=!1;return e._autoprefixerDisabled=r,r}reduceSpaces(e){let t=!1;if(this.prefixes.group(e).up((()=>(t=!0,!0))),t)return;let r=e.raw("before").split("\n"),n=r[r.length-1].length,i=!1;this.prefixes.group(e).down((e=>{r=e.raw("before").split("\n");let t=r.length-1;r[t].length>n&&(!1===i&&(i=r[t].length-n),r[t]=r[t].slice(0,-i),e.raws.before=r.join("\n"))}))}displayType(e){for(let t of e.parent.nodes)if("display"===t.prop){if(t.value.includes("flex"))return"flex";if(t.value.includes("grid"))return"grid"}return!1}gridStatus(t,r){if(!t)return!1;if(void 0!==t._autoprefixerGridStatus)return t._autoprefixerGridStatus;let n=null;if(t.nodes){let e;t.each((t=>{if("comment"===t.type&&c.test(t.text)){let n=/:\s*autoplace/i.test(t.text),i=/no-autoplace/i.test(t.text);void 0!==e?r.warn("Second Autoprefixer grid control comment was ignored. Autoprefixer applies control comments to the whole block, not to the next rules.",{node:t}):e=n?"autoplace":!!i||/on/i.test(t.text)}})),void 0!==e&&(n=e)}if("atrule"===t.type&&"supports"===t.name){let e=t.params;e.includes("grid")&&e.includes("auto")&&(n=!1)}if(!t.nodes||null===n)if(t.parent){let e=this.gridStatus(t.parent,r);n=!0!==t.parent._autoprefixerSelfDisabled&&e}else n=void 0!==this.prefixes.options.grid?this.prefixes.options.grid:void 0!==e.env.AUTOPREFIXER_GRID&&("autoplace"!==e.env.AUTOPREFIXER_GRID||"autoplace");return t._autoprefixerGridStatus=n,n}}})),bl=f(((e,t)=>{g(),t.exports={A:{A:{2:"K E F G A B JC"},B:{1:"C L M H N D O P Q R S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I"},C:{1:"2 3 4 5 6 7 8 9 AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB 0B dB 1B eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R 2B S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I uB 3B 4B",2:"0 1 KC zB J K E F G A B C L M H N D O k l LC MC"},D:{1:"8 9 AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB 0B dB 1B eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I uB 3B 4B",2:"0 1 2 3 4 5 6 7 J K E F G A B C L M H N D O k l"},E:{1:"G A B C L M H D RC 6B vB wB 7B SC TC 8B 9B xB AC yB BC CC DC EC FC GC UC",2:"0 J K E F NC 5B OC PC QC"},F:{1:"1 2 3 4 5 6 7 8 9 H N D O k l AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB dB eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R 2B S T U V W X Y Z a b c d e f g h i j wB",2:"G B C VC WC XC YC vB HC ZC"},G:{1:"D fC gC hC iC jC kC lC mC nC oC pC qC rC sC tC 8B 9B xB AC yB BC CC DC EC FC GC",2:"F 5B aC IC bC cC dC eC"},H:{1:"uC"},I:{1:"I zC 0C",2:"zB J vC wC xC yC IC"},J:{2:"E A"},K:{1:"m",2:"A B C vB HC wB"},L:{1:"I"},M:{1:"uB"},N:{2:"A B"},O:{1:"xB"},P:{1:"J k l 1C 2C 3C 4C 5C 6B 6C 7C 8C 9C AD yB BD CD DD"},Q:{1:"7B"},R:{1:"ED"},S:{1:"FD GD"}},B:4,C:"CSS Feature Queries"}})),vl=f(((e,t)=>{function r(e){return e[e.length-1]}g();var n={parse(e){let t=[""],n=[t];for(let i of e)"("!==i?")"!==i?t[t.length-1]+=i:(n.pop(),t=r(n),t.push("")):(t=[""],r(n).push(t),n.push(t));return n[0]},stringify(e){let t="";for(let r of e)t+="object"!=typeof r?r:`(${n.stringify(r)})`;return t}};t.exports=n})),wl=f(((e,t)=>{g();var r=bl(),{feature:n}=(Za(),Ja),{parse:i}=xr(),o=rl(),s=vl(),a=ml(),l=tl(),c=n(r),u=[];for(let e in c.stats){let t=c.stats[e];for(let r in t){let n=t[r];/y/.test(n)&&u.push(e+" "+r)}}t.exports=class{constructor(e,t){this.Prefixes=e,this.all=t}prefixer(){if(this.prefixerCache)return this.prefixerCache;let e=this.all.browsers.selected.filter((e=>u.includes(e))),t=new o(this.all.browsers.data,e,this.all.options);return this.prefixerCache=new this.Prefixes(this.all.data,t,this.all.options),this.prefixerCache}parse(e){let t=e.split(":"),r=t[0],n=t[1];return n||(n=""),[r.trim(),n.trim()]}virtual(e){let[t,r]=this.parse(e),n=i("a{}").first;return n.append({prop:t,value:r,raws:{before:""}}),n}prefixed(e){let t=this.virtual(e);if(this.disabled(t.first))return t.nodes;let r=this.prefixer().add[t.first.prop];r&&r.process&&r.process(t.first,{warn:()=>null});for(let e of t.nodes){for(let r of this.prefixer().values("add",t.first.prop))r.process(e);a.save(this.all,e)}return t.nodes}isNot(e){return"string"==typeof e&&/not\s*/i.test(e)}isOr(e){return"string"==typeof e&&/\s*or\s*/i.test(e)}isProp(e){return"object"==typeof e&&1===e.length&&"string"==typeof e[0]}isHack(e,t){return!new RegExp(`(\\(|\\s)${l.escapeRegexp(t)}:`).test(e)}toRemove(e,t){let[r,n]=this.parse(e),i=this.all.unprefixed(r),o=this.all.cleaner();if(o.remove[r]&&o.remove[r].remove&&!this.isHack(t,i))return!0;for(let e of o.values("remove",i))if(e.check(n))return!0;return!1}remove(e,t){let r=0;for(;r"object"!=typeof e?e:1===e.length&&"object"==typeof e[0]?this.cleanBrackets(e[0]):this.cleanBrackets(e)))}convert(e){let t=[""];for(let r of e)t.push([`${r.prop}: ${r.value}`]),t.push(" or ");return t[t.length-1]="",t}normalize(e){if("object"!=typeof e)return e;if("string"==typeof(e=e.filter((e=>""!==e)))[0]){let t=e[0].trim();if(t.includes(":")||"selector"===t||"not selector"===t)return[s.stringify(e)]}return e.map((e=>this.normalize(e)))}add(e,t){return e.map((e=>{if(this.isProp(e)){let t=this.prefixed(e[0]);return t.length>1?this.convert(t):e}return"object"==typeof e?this.add(e,t):e}))}process(e){let t=s.parse(e.params);t=this.normalize(t),t=this.remove(t,e.params),t=this.add(t,e.params),t=this.cleanBrackets(t),e.params=s.stringify(t)}disabled(e){if(!this.all.options.grid&&("display"===e.prop&&e.value.includes("grid")||e.prop.includes("grid")||"justify-items"===e.prop))return!0;if(!1===this.all.options.flexbox){if("display"===e.prop&&e.value.includes("flex"))return!0;let t=["order","justify-content","align-items","align-content"];if(e.prop.includes("flex")||t.includes(e.prop))return!0}return!1}}})),xl=f(((e,t)=>{g(),t.exports=class{constructor(e,t){this.prefix=t,this.prefixed=e.prefixed(this.prefix),this.regexp=e.regexp(this.prefix),this.prefixeds=e.possible().map((t=>[e.prefixed(t),e.regexp(t)])),this.unprefixed=e.name,this.nameRegexp=e.regexp()}isHack(e){let t=e.parent.index(e)+1,r=e.parent.nodes;for(;t{g();var{list:r}=xr(),n=xl(),i=il(),o=rl(),s=tl();t.exports=class extends i{constructor(e,t,r){super(e,t,r),this.regexpCache=new Map}check(e){return!!e.selector.includes(this.name)&&!!e.selector.match(this.regexp())}prefixed(e){return this.name.replace(/^(\W*)/,`$1${e}`)}regexp(e){if(!this.regexpCache.has(e)){let t=e?this.prefixed(e):this.name;this.regexpCache.set(e,new RegExp(`(^|[^:"'=])${s.escapeRegexp(t)}`,"gi"))}return this.regexpCache.get(e)}possible(){return o.prefixes()}prefixeds(e){if(e._autoprefixerPrefixeds){if(e._autoprefixerPrefixeds[this.name])return e._autoprefixerPrefixeds}else e._autoprefixerPrefixeds={};let t={};if(e.selector.includes(",")){let n=r.comma(e.selector).filter((e=>e.includes(this.name)));for(let e of this.possible())t[e]=n.map((t=>this.replace(t,e))).join(", ")}else for(let r of this.possible())t[r]=this.replace(e.selector,r);return e._autoprefixerPrefixeds[this.name]=t,e._autoprefixerPrefixeds}already(e,t,r){let n=e.parent.index(e)-1;for(;n>=0;){let i=e.parent.nodes[n];if("rule"!==i.type)return!1;let o=!1;for(let e in t[this.name]){let n=t[this.name][e];if(i.selector===n){if(r===e)return!0;o=!0;break}}if(!o)return!1;n-=1}return!1}replace(e,t){return e.replace(this.regexp(),`$1${this.prefixed(t)}`)}add(e,t){let r=this.prefixeds(e);if(this.already(e,r,t))return;let n=this.clone(e,{selector:r[this.name][t]});e.parent.insertBefore(e,n)}old(e){return new n(this,e)}}})),Sl=f(((e,t)=>{g();var r=il();t.exports=class extends r{add(e,t){let r=t+e.name;if(e.parent.some((t=>t.name===r&&t.params===e.params)))return;let n=this.clone(e,{name:r});return e.parent.insertBefore(e,n)}process(e){let t=this.parentPrefix(e);for(let r of this.prefixes)(!t||t===r)&&this.add(e,r)}}})),Cl=f(((e,t)=>{g();var r=kl(),n=class extends r{prefixed(e){return"-webkit-"===e?":-webkit-full-screen":"-moz-"===e?":-moz-full-screen":`:${e}fullscreen`}};n.names=[":fullscreen"],t.exports=n})),Al=f(((e,t)=>{g();var r=kl(),n=class extends r{possible(){return super.possible().concat(["-moz- old","-ms- old"])}prefixed(e){return"-webkit-"===e?"::-webkit-input-placeholder":"-ms-"===e?"::-ms-input-placeholder":"-ms- old"===e?":-ms-input-placeholder":"-moz- old"===e?":-moz-placeholder":`::${e}placeholder`}};n.names=["::placeholder"],t.exports=n})),Ol=f(((e,t)=>{g();var r=kl(),n=class extends r{prefixed(e){return"-ms-"===e?":-ms-input-placeholder":`:${e}placeholder-shown`}};n.names=[":placeholder-shown"],t.exports=n})),El=f(((e,t)=>{g();var r=kl(),n=tl(),i=class extends r{constructor(e,t,r){super(e,t,r),this.prefixes&&(this.prefixes=n.uniq(this.prefixes.map((e=>"-webkit-"))))}prefixed(e){return"-webkit-"===e?"::-webkit-file-upload-button":`::${e}file-selector-button`}};i.names=["::file-selector-button"],t.exports=i})),_l=f(((e,t)=>{g(),t.exports=function(e){let t;return"-webkit- 2009"===e||"-moz-"===e?t=2009:"-ms-"===e?t=2012:"-webkit-"===e&&(t="final"),"-webkit- 2009"===e&&(e="-webkit-"),[t,e]}})),Tl=f(((e,t)=>{g();var r=xr().list,n=_l(),i=ol(),o=class extends i{prefixed(e,t){let r;return[r,t]=n(t),2009===r?t+"box-flex":super.prefixed(e,t)}normalize(){return"flex"}set(e,t){let i=n(t)[0];if(2009===i)return e.value=r.space(e.value)[0],e.value=o.oldValues[e.value]||e.value,super.set(e,t);if(2012===i){let t=r.space(e.value);3===t.length&&"0"===t[2]&&(e.value=t.slice(0,2).concat("0px").join(" "))}return super.set(e,t)}};o.names=["flex","box-flex"],o.oldValues={auto:"1",none:"0"},t.exports=o})),Pl=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{prefixed(e,t){let n;return[n,t]=r(t),2009===n?t+"box-ordinal-group":2012===n?t+"flex-order":super.prefixed(e,t)}normalize(){return"order"}set(e,t){return 2009===r(t)[0]&&/\d/.test(e.value)?(e.value=(parseInt(e.value)+1).toString(),super.set(e,t)):super.set(e,t)}};i.names=["order","flex-order","box-ordinal-group"],t.exports=i})),jl=f(((e,t)=>{g();var r=ol(),n=class extends r{check(e){let t=e.value;return!t.toLowerCase().includes("alpha(")&&!t.includes("DXImageTransform.Microsoft")&&!t.includes("data:image/svg+xml")}};n.names=["filter"],t.exports=n})),Il=f(((e,t)=>{g();var r=ol(),n=class extends r{insert(e,t,r,n){if("-ms-"!==t)return super.insert(e,t,r);let i=this.clone(e),o=e.prop.replace(/end$/,"start"),s=t+e.prop.replace(/end$/,"span");if(!e.parent.some((e=>e.prop===s))){if(i.prop=s,e.value.includes("span"))i.value=e.value.replace(/span\s/i,"");else{let t;if(e.parent.walkDecls(o,(e=>{t=e})),t){let r=Number(e.value)-Number(t.value)+"";i.value=r}else e.warn(n,`Can not prefix ${e.prop} (${o} is not found)`)}e.cloneBefore(i)}}};n.names=["grid-row-end","grid-column-end"],t.exports=n})),Dl=f(((e,t)=>{g();var r=ol(),n=class extends r{check(e){return!e.value.split(/\s+/).some((e=>{let t=e.toLowerCase();return"reverse"===t||"alternate-reverse"===t}))}};n.names=["animation","animation-direction"],t.exports=n})),$l=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{insert(e,t,n){let i;if([i,t]=r(t),2009!==i)return super.insert(e,t,n);let o=e.value.split(/\s+/).filter((e=>"wrap"!==e&&"nowrap"!==e&&"wrap-reverse"));if(0===o.length||e.parent.some((e=>e.prop===t+"box-orient"||e.prop===t+"box-direction")))return;let s=o[0],a=s.includes("row")?"horizontal":"vertical",l=s.includes("reverse")?"reverse":"normal",c=this.clone(e);return c.prop=t+"box-orient",c.value=a,this.needCascade(e)&&(c.raws.before=this.calcBefore(n,e,t)),e.parent.insertBefore(e,c),c=this.clone(e),c.prop=t+"box-direction",c.value=l,this.needCascade(e)&&(c.raws.before=this.calcBefore(n,e,t)),e.parent.insertBefore(e,c)}};i.names=["flex-flow","box-direction","box-orient"],t.exports=i})),Bl=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{normalize(){return"flex"}prefixed(e,t){let n;return[n,t]=r(t),2009===n?t+"box-flex":2012===n?t+"flex-positive":super.prefixed(e,t)}};i.names=["flex-grow","flex-positive"],t.exports=i})),Rl=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{set(e,t){if(2009!==r(t)[0])return super.set(e,t)}};i.names=["flex-wrap"],t.exports=i})),Ml=f(((e,t)=>{g();var r=ol(),n=gl(),i=class extends r{insert(e,t,r,i){if("-ms-"!==t)return super.insert(e,t,r);let o=n.parse(e),[s,a]=n.translate(o,0,2),[l,c]=n.translate(o,1,3);[["grid-row",s],["grid-row-span",a],["grid-column",l],["grid-column-span",c]].forEach((([t,r])=>{n.insertDecl(e,t,r)})),n.warnTemplateSelectorNotFound(e,i),n.warnIfGridRowColumnExists(e,i)}};i.names=["grid-area"],t.exports=i})),Ul=f(((e,t)=>{g();var r=ol(),n=gl(),i=class extends r{insert(e,t,r){if("-ms-"!==t)return super.insert(e,t,r);if(e.parent.some((e=>"-ms-grid-row-align"===e.prop)))return;let[[i,o]]=n.parse(e);o?(n.insertDecl(e,"grid-row-align",i),n.insertDecl(e,"grid-column-align",o)):(n.insertDecl(e,"grid-row-align",i),n.insertDecl(e,"grid-column-align",i))}};i.names=["place-self"],t.exports=i})),zl=f(((e,t)=>{g();var r=ol(),n=class extends r{check(e){let t=e.value;return!t.includes("/")||t.includes("span")}normalize(e){return e.replace("-start","")}prefixed(e,t){let r=super.prefixed(e,t);return"-ms-"===t&&(r=r.replace("-start","")),r}};n.names=["grid-row-start","grid-column-start"],t.exports=n})),Ll=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{check(e){return e.parent&&!e.parent.some((e=>e.prop&&e.prop.startsWith("grid-")))}prefixed(e,t){let n;return[n,t]=r(t),2012===n?t+"flex-item-align":super.prefixed(e,t)}normalize(){return"align-self"}set(e,t){let n=r(t)[0];return 2012===n?(e.value=i.oldValues[e.value]||e.value,super.set(e,t)):"final"===n?super.set(e,t):void 0}};i.names=["align-self","flex-item-align"],i.oldValues={"flex-end":"end","flex-start":"start"},t.exports=i})),Fl=f(((e,t)=>{g();var r=ol(),n=tl(),i=class extends r{constructor(e,t,r){super(e,t,r),this.prefixes&&(this.prefixes=n.uniq(this.prefixes.map((e=>"-ms-"===e?"-webkit-":e))))}};i.names=["appearance"],t.exports=i})),Nl=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{normalize(){return"flex-basis"}prefixed(e,t){let n;return[n,t]=r(t),2012===n?t+"flex-preferred-size":super.prefixed(e,t)}set(e,t){let n;if([n,t]=r(t),2012===n||"final"===n)return super.set(e,t)}};i.names=["flex-basis","flex-preferred-size"],t.exports=i})),Vl=f(((e,t)=>{g();var r=ol(),n=class extends r{normalize(){return this.name.replace("box-image","border")}prefixed(e,t){let r=super.prefixed(e,t);return"-webkit-"===t&&(r=r.replace("border","box-image")),r}};n.names=["mask-border","mask-border-source","mask-border-slice","mask-border-width","mask-border-outset","mask-border-repeat","mask-box-image","mask-box-image-source","mask-box-image-slice","mask-box-image-width","mask-box-image-outset","mask-box-image-repeat"],t.exports=n})),Wl=f(((e,t)=>{g();var r=ol(),n=class extends r{insert(e,t,r){let i,o="mask-composite"===e.prop;i=o?e.value.split(","):e.value.match(n.regexp)||[],i=i.map((e=>e.trim())).filter((e=>e));let s,a=i.length;if(a&&(s=this.clone(e),s.value=i.map((e=>n.oldValues[e]||e)).join(", "),i.includes("intersect")&&(s.value+=", xor"),s.prop=t+"mask-composite"),o)return a?(this.needCascade(e)&&(s.raws.before=this.calcBefore(r,e,t)),e.parent.insertBefore(e,s)):void 0;let l=this.clone(e);return l.prop=t+l.prop,a&&(l.value=l.value.replace(n.regexp,"")),this.needCascade(e)&&(l.raws.before=this.calcBefore(r,e,t)),e.parent.insertBefore(e,l),a?(this.needCascade(e)&&(s.raws.before=this.calcBefore(r,e,t)),e.parent.insertBefore(e,s)):e}};n.names=["mask","mask-composite"],n.oldValues={add:"source-over",subtract:"source-out",intersect:"source-in",exclude:"xor"},n.regexp=new RegExp(`\\s+(${Object.keys(n.oldValues).join("|")})\\b(?!\\))\\s*(?=[,])`,"ig"),t.exports=n})),ql=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{prefixed(e,t){let n;return[n,t]=r(t),2009===n?t+"box-align":2012===n?t+"flex-align":super.prefixed(e,t)}normalize(){return"align-items"}set(e,t){let n=r(t)[0];return(2009===n||2012===n)&&(e.value=i.oldValues[e.value]||e.value),super.set(e,t)}};i.names=["align-items","flex-align","box-align"],i.oldValues={"flex-end":"end","flex-start":"start"},t.exports=i})),Gl=f(((e,t)=>{g();var r=ol(),n=class extends r{set(e,t){return"-ms-"===t&&"contain"===e.value&&(e.value="element"),super.set(e,t)}insert(e,t,r){if("all"!==e.value||"-ms-"!==t)return super.insert(e,t,r)}};n.names=["user-select"],t.exports=n})),Yl=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{normalize(){return"flex-shrink"}prefixed(e,t){let n;return[n,t]=r(t),2012===n?t+"flex-negative":super.prefixed(e,t)}set(e,t){let n;if([n,t]=r(t),2012===n||"final"===n)return super.set(e,t)}};i.names=["flex-shrink","flex-negative"],t.exports=i})),Hl=f(((e,t)=>{g();var r=ol(),n=class extends r{prefixed(e,t){return`${t}column-${e}`}normalize(e){return e.includes("inside")?"break-inside":e.includes("before")?"break-before":"break-after"}set(e,t){return("break-inside"===e.prop&&"avoid-column"===e.value||"avoid-page"===e.value)&&(e.value="avoid"),super.set(e,t)}insert(e,t,r){return"break-inside"!==e.prop?super.insert(e,t,r):/region/i.test(e.value)||/page/i.test(e.value)?void 0:super.insert(e,t,r)}};n.names=["break-inside","page-break-inside","column-break-inside","break-before","page-break-before","column-break-before","break-after","page-break-after","column-break-after"],t.exports=n})),Ql=f(((e,t)=>{g();var r=ol(),n=class extends r{prefixed(e,t){return t+"print-color-adjust"}normalize(){return"color-adjust"}};n.names=["color-adjust","print-color-adjust"],t.exports=n})),Jl=f(((e,t)=>{g();var r=ol(),n=class extends r{insert(e,t,r){if("-ms-"===t){let i=this.set(this.clone(e),t);this.needCascade(e)&&(i.raws.before=this.calcBefore(r,e,t));let o="ltr";return e.parent.nodes.forEach((e=>{"direction"===e.prop&&("rtl"===e.value||"ltr"===e.value)&&(o=e.value)})),i.value=n.msValues[o][e.value]||e.value,e.parent.insertBefore(e,i)}return super.insert(e,t,r)}};n.names=["writing-mode"],n.msValues={ltr:{"horizontal-tb":"lr-tb","vertical-rl":"tb-rl","vertical-lr":"tb-lr"},rtl:{"horizontal-tb":"rl-tb","vertical-rl":"bt-rl","vertical-lr":"bt-lr"}},t.exports=n})),Xl=f(((e,t)=>{g();var r=ol(),n=class extends r{set(e,t){return e.value=e.value.replace(/\s+fill(\s)/,"$1"),super.set(e,t)}};n.names=["border-image"],t.exports=n})),Kl=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{prefixed(e,t){let n;return[n,t]=r(t),2012===n?t+"flex-line-pack":super.prefixed(e,t)}normalize(){return"align-content"}set(e,t){let n=r(t)[0];return 2012===n?(e.value=i.oldValues[e.value]||e.value,super.set(e,t)):"final"===n?super.set(e,t):void 0}};i.names=["align-content","flex-line-pack"],i.oldValues={"flex-end":"end","flex-start":"start","space-between":"justify","space-around":"distribute"},t.exports=i})),Zl=f(((e,t)=>{g();var r=ol(),n=class extends r{prefixed(e,t){return"-moz-"===t?t+(n.toMozilla[e]||e):super.prefixed(e,t)}normalize(e){return n.toNormal[e]||e}};n.names=["border-radius"],n.toMozilla={},n.toNormal={};for(let e of["top","bottom"])for(let t of["left","right"]){let r=`border-${e}-${t}-radius`,i=`border-radius-${e}${t}`;n.names.push(r),n.names.push(i),n.toMozilla[r]=i,n.toNormal[i]=r}t.exports=n})),ec=f(((e,t)=>{g();var r=ol(),n=class extends r{prefixed(e,t){return e.includes("-start")?t+e.replace("-block-start","-before"):t+e.replace("-block-end","-after")}normalize(e){return e.includes("-before")?e.replace("-before","-block-start"):e.replace("-after","-block-end")}};n.names=["border-block-start","border-block-end","margin-block-start","margin-block-end","padding-block-start","padding-block-end","border-before","border-after","margin-before","margin-after","padding-before","padding-after"],t.exports=n})),tc=f(((e,t)=>{g();var r=ol(),{parseTemplate:n,warnMissedAreas:i,getGridGap:o,warnGridGap:s,inheritGridGap:a}=gl(),l=class extends r{insert(e,t,r,l){if("-ms-"!==t)return super.insert(e,t,r);if(e.parent.some((e=>"-ms-grid-rows"===e.prop)))return;let c=o(e),u=a(e,c),{rows:d,columns:p,areas:f}=n({decl:e,gap:u||c}),h=Object.keys(f).length>0,m=Boolean(d),g=Boolean(p);return s({gap:c,hasColumns:g,decl:e,result:l}),i(f,e,l),(m&&g||h)&&e.cloneBefore({prop:"-ms-grid-rows",value:d,raws:{}}),g&&e.cloneBefore({prop:"-ms-grid-columns",value:p,raws:{}}),e}};l.names=["grid-template"],t.exports=l})),rc=f(((e,t)=>{g();var r=ol(),n=class extends r{prefixed(e,t){return t+e.replace("-inline","")}normalize(e){return e.replace(/(margin|padding|border)-(start|end)/,"$1-inline-$2")}};n.names=["border-inline-start","border-inline-end","margin-inline-start","margin-inline-end","padding-inline-start","padding-inline-end","border-start","border-end","margin-start","margin-end","padding-start","padding-end"],t.exports=n})),nc=f(((e,t)=>{g();var r=ol(),n=class extends r{check(e){return!e.value.includes("flex-")&&"baseline"!==e.value}prefixed(e,t){return t+"grid-row-align"}normalize(){return"align-self"}};n.names=["grid-row-align"],t.exports=n})),ic=f(((e,t)=>{g();var r=ol(),n=class extends r{keyframeParents(e){let{parent:t}=e;for(;t;){if("atrule"===t.type&&"keyframes"===t.name)return!0;({parent:t}=t)}return!1}contain3d(e){if("transform-origin"===e.prop)return!1;for(let t of n.functions3d)if(e.value.includes(`${t}(`))return!0;return!1}set(e,t){return e=super.set(e,t),"-ms-"===t&&(e.value=e.value.replace(/rotatez/gi,"rotate")),e}insert(e,t,r){if("-ms-"===t){if(!this.contain3d(e)&&!this.keyframeParents(e))return super.insert(e,t,r)}else{if("-o-"!==t)return super.insert(e,t,r);if(!this.contain3d(e))return super.insert(e,t,r)}}};n.names=["transform","transform-origin"],n.functions3d=["matrix3d","translate3d","translateZ","scale3d","scaleZ","rotate3d","rotateX","rotateY","perspective"],t.exports=n})),oc=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{normalize(){return"flex-direction"}insert(e,t,n){let i;if([i,t]=r(t),2009!==i)return super.insert(e,t,n);if(e.parent.some((e=>e.prop===t+"box-orient"||e.prop===t+"box-direction")))return;let o,s,a=e.value;"inherit"===a||"initial"===a||"unset"===a?(o=a,s=a):(o=a.includes("row")?"horizontal":"vertical",s=a.includes("reverse")?"reverse":"normal");let l=this.clone(e);return l.prop=t+"box-orient",l.value=o,this.needCascade(e)&&(l.raws.before=this.calcBefore(n,e,t)),e.parent.insertBefore(e,l),l=this.clone(e),l.prop=t+"box-direction",l.value=s,this.needCascade(e)&&(l.raws.before=this.calcBefore(n,e,t)),e.parent.insertBefore(e,l)}old(e,t){let n;return[n,t]=r(t),2009===n?[t+"box-orient",t+"box-direction"]:super.old(e,t)}};i.names=["flex-direction","box-direction","box-orient"],t.exports=i})),sc=f(((e,t)=>{g();var r=ol(),n=class extends r{check(e){return"pixelated"===e.value}prefixed(e,t){return"-ms-"===t?"-ms-interpolation-mode":super.prefixed(e,t)}set(e,t){return"-ms-"!==t?super.set(e,t):(e.prop="-ms-interpolation-mode",e.value="nearest-neighbor",e)}normalize(){return"image-rendering"}process(e,t){return super.process(e,t)}};n.names=["image-rendering","interpolation-mode"],t.exports=n})),ac=f(((e,t)=>{g();var r=ol(),n=tl(),i=class extends r{constructor(e,t,r){super(e,t,r),this.prefixes&&(this.prefixes=n.uniq(this.prefixes.map((e=>"-ms-"===e?"-webkit-":e))))}};i.names=["backdrop-filter"],t.exports=i})),lc=f(((e,t)=>{g();var r=ol(),n=tl(),i=class extends r{constructor(e,t,r){super(e,t,r),this.prefixes&&(this.prefixes=n.uniq(this.prefixes.map((e=>"-ms-"===e?"-webkit-":e))))}check(e){return"text"===e.value.toLowerCase()}};i.names=["background-clip"],t.exports=i})),cc=f(((e,t)=>{g();var r=ol(),n=["none","underline","overline","line-through","blink","inherit","initial","unset"],i=class extends r{check(e){return e.value.split(/\s+/).some((e=>!n.includes(e)))}};i.names=["text-decoration"],t.exports=i})),uc=f(((e,t)=>{g();var r=_l(),n=ol(),i=class extends n{prefixed(e,t){let n;return[n,t]=r(t),2009===n?t+"box-pack":2012===n?t+"flex-pack":super.prefixed(e,t)}normalize(){return"justify-content"}set(e,t){let n=r(t)[0];if(2009===n||2012===n){let r=i.oldValues[e.value]||e.value;if(e.value=r,2009!==n||"distribute"!==r)return super.set(e,t)}else if("final"===n)return super.set(e,t)}};i.names=["justify-content","flex-pack","box-pack"],i.oldValues={"flex-end":"end","flex-start":"start","space-between":"justify","space-around":"distribute"},t.exports=i})),dc=f(((e,t)=>{g();var r=ol(),n=class extends r{set(e,t){let r=e.value.toLowerCase();return"-webkit-"===t&&!r.includes(" ")&&"contain"!==r&&"cover"!==r&&(e.value=e.value+" "+e.value),super.set(e,t)}};n.names=["background-size"],t.exports=n})),pc=f(((e,t)=>{g();var r=ol(),n=gl(),i=class extends r{insert(e,t,r){if("-ms-"!==t)return super.insert(e,t,r);let i=n.parse(e),[o,s]=n.translate(i,0,1);i[0]&&i[0].includes("span")&&(s=i[0].join("").replace(/\D/g,"")),[[e.prop,o],[`${e.prop}-span`,s]].forEach((([t,r])=>{n.insertDecl(e,t,r)}))}};i.names=["grid-row","grid-column"],t.exports=i})),fc=f(((e,t)=>{g();var r=ol(),{prefixTrackProp:n,prefixTrackValue:i,autoplaceGridItems:o,getGridGap:s,inheritGridGap:a}=gl(),l=yl(),c=class extends r{prefixed(e,t){return"-ms-"===t?n({prop:e,prefix:t}):super.prefixed(e,t)}normalize(e){return e.replace(/^grid-(rows|columns)/,"grid-template-$1")}insert(e,t,r,c){if("-ms-"!==t)return super.insert(e,t,r);let{parent:u,prop:d,value:p}=e,f=d.includes("rows"),h=d.includes("columns"),m=u.some((e=>"grid-template"===e.prop||"grid-template-areas"===e.prop));if(m&&f)return!1;let g=new l({options:{}}),y=g.gridStatus(u,c),b=s(e);b=a(e,b)||b;let v=f?b.row:b.column;("no-autoplace"===y||!0===y)&&!m&&(v=null);let w=i({value:p,gap:v});e.cloneBefore({prop:n({prop:d,prefix:t}),value:w});let x=u.nodes.find((e=>"grid-auto-flow"===e.prop)),k="row";if(x&&!g.disabled(x,c)&&(k=x.value.trim()),"autoplace"===y){let t=u.nodes.find((e=>"grid-template-rows"===e.prop));if(!t&&m)return;if(!t&&!m)return void e.warn(c,"Autoplacement does not work without grid-template-rows property");!u.nodes.find((e=>"grid-template-columns"===e.prop))&&!m&&e.warn(c,"Autoplacement does not work without grid-template-columns property"),h&&!m&&o(e,c,b,k)}}};c.names=["grid-template-rows","grid-template-columns","grid-rows","grid-columns"],t.exports=c})),hc=f(((e,t)=>{g();var r=ol(),n=class extends r{check(e){return!e.value.includes("flex-")&&"baseline"!==e.value}prefixed(e,t){return t+"grid-column-align"}normalize(){return"justify-self"}};n.names=["grid-column-align"],t.exports=n})),mc=f(((e,t)=>{g();var r=ol(),n=class extends r{prefixed(e,t){return t+"scroll-chaining"}normalize(){return"overscroll-behavior"}set(e,t){return"auto"===e.value?e.value="chained":("none"===e.value||"contain"===e.value)&&(e.value="none"),super.set(e,t)}};n.names=["overscroll-behavior","scroll-chaining"],t.exports=n})),gc=f(((e,t)=>{g();var r=ol(),{parseGridAreas:n,warnMissedAreas:i,prefixTrackProp:o,prefixTrackValue:s,getGridGap:a,warnGridGap:l,inheritGridGap:c}=gl(),u=class extends r{insert(e,t,r,u){if("-ms-"!==t)return super.insert(e,t,r);let d=!1,p=!1,f=e.parent,h=a(e);h=c(e,h)||h,f.walkDecls(/-ms-grid-rows/,(e=>e.remove())),f.walkDecls(/grid-template-(rows|columns)/,(e=>{if("grid-template-rows"===e.prop){p=!0;let{prop:r,value:n}=e;e.cloneBefore({prop:o({prop:r,prefix:t}),value:s({value:n,gap:h.row})})}else d=!0}));let m=e.value.trim().slice(1,-1).split(/["']\s*["']?/g);d&&!p&&h.row&&m.length>1&&e.cloneBefore({prop:"-ms-grid-rows",value:s({value:`repeat(${m.length}, auto)`,gap:h.row}),raws:{}}),l({gap:h,hasColumns:d,decl:e,result:u});let g=n({rows:m,gap:h});return i(g,e,u),e}};u.names=["grid-template-areas"],t.exports=u})),yc=f(((e,t)=>{g();var r=ol(),n=class extends r{set(e,t){return"-webkit-"===t&&(e.value=e.value.replace(/\s*(right|left)\s*/i,"")),super.set(e,t)}};n.names=["text-emphasis-position"],t.exports=n})),bc=f(((e,t)=>{g();var r=ol(),n=class extends r{set(e,t){return"text-decoration-skip-ink"===e.prop&&"auto"===e.value?(e.prop=t+"text-decoration-skip",e.value="ink",e):super.set(e,t)}};n.names=["text-decoration-skip-ink","text-decoration-skip"],t.exports=n})),vc=f(((e,t)=>{function r(e,t,r){var n=t-e;return((r-e)%n+n)%n+e}function n(e,t,r){return Math.max(e,Math.min(t,r))}function i(e,t,r,n,i){if(!o(e,t,r,n,i))throw new Error(r+" is outside of range ["+e+","+t+")");return r}function o(e,t,r,n,i){return!(rt||i&&r===t||n&&r===e)}function s(e,t,r,n){return(r?"(":"[")+e+","+t+(n?")":"]")}g(),t.exports={wrap:r,limit:n,validate:i,test:o,curry:function(e,t,a,l){var c=s.bind(null,e,t,a,l);return{wrap:r.bind(null,e,t),limit:n.bind(null,e,t),validate:function(r){return i(e,t,r,a,l)},test:function(r){return o(e,t,r,a,l)},toString:c,name:c}},name:s}})),wc=f(((e,t)=>{g();var r=pl(),n=vc(),i=hl(),o=ml(),s=tl(),a=/top|left|right|bottom/gi,l=class extends o{replace(e,t){let n=r(e);for(let e of n.nodes)if("function"===e.type&&e.value===this.name)if(e.nodes=this.newDirection(e.nodes),e.nodes=this.normalize(e.nodes),"-webkit- old"===t){if(!this.oldWebkit(e))return!1}else e.nodes=this.convertDirection(e.nodes),e.value=t+e.value;return n.toString()}replaceFirst(e,...t){return t.map((e=>" "===e?{type:"space",value:e}:{type:"word",value:e})).concat(e.slice(1))}normalizeUnit(e,t){return parseFloat(e)/t*360+"deg"}normalize(e){if(!e[0])return e;if(/-?\d+(.\d+)?grad/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,400);else if(/-?\d+(.\d+)?rad/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,2*Math.PI);else if(/-?\d+(.\d+)?turn/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,1);else if(e[0].value.includes("deg")){let t=parseFloat(e[0].value);t=n.wrap(0,360,t),e[0].value=`${t}deg`}return"0deg"===e[0].value?e=this.replaceFirst(e,"to"," ","top"):"90deg"===e[0].value?e=this.replaceFirst(e,"to"," ","right"):"180deg"===e[0].value?e=this.replaceFirst(e,"to"," ","bottom"):"270deg"===e[0].value&&(e=this.replaceFirst(e,"to"," ","left")),e}newDirection(e){if("to"===e[0].value||(a.lastIndex=0,!a.test(e[0].value)))return e;e.unshift({type:"word",value:"to"},{type:"space",value:" "});for(let t=2;t0&&("to"===e[0].value?this.fixDirection(e):e[0].value.includes("deg")?this.fixAngle(e):this.isRadial(e)&&this.fixRadial(e)),e}fixDirection(e){e.splice(0,2);for(let t of e){if("div"===t.type)break;"word"===t.type&&(t.value=this.revertDirection(t.value))}}fixAngle(e){let t=e[0].value;t=parseFloat(t),t=Math.abs(450-t)%360,t=this.roundFloat(t,3),e[0].value=`${t}deg`}fixRadial(e){let t,r,n,i,o,s,a=[],l=[];for(i=0;i{g();var r=hl(),n=ml();function i(e){return new RegExp(`(^|[\\s,(])(${e}($|[\\s),]))`,"gi")}var o=class extends n{regexp(){return this.regexpCache||(this.regexpCache=i(this.name)),this.regexpCache}isStretch(){return"stretch"===this.name||"fill"===this.name||"fill-available"===this.name}replace(e,t){return"-moz-"===t&&this.isStretch()?e.replace(this.regexp(),"$1-moz-available$3"):"-webkit-"===t&&this.isStretch()?e.replace(this.regexp(),"$1-webkit-fill-available$3"):super.replace(e,t)}old(e){let t=e+this.name;return this.isStretch()&&("-moz-"===e?t="-moz-available":"-webkit-"===e&&(t="-webkit-fill-available")),new r(this.name,t,t,i(t))}add(e,t){if(!e.prop.includes("grid")||"-webkit-"===t)return super.add(e,t)}};o.names=["max-content","min-content","fit-content","fill","fill-available","stretch"],t.exports=o})),kc=f(((e,t)=>{g();var r=hl(),n=ml(),i=class extends n{replace(e,t){return"-webkit-"===t?e.replace(this.regexp(),"$1-webkit-optimize-contrast"):"-moz-"===t?e.replace(this.regexp(),"$1-moz-crisp-edges"):super.replace(e,t)}old(e){return"-webkit-"===e?new r(this.name,"-webkit-optimize-contrast"):"-moz-"===e?new r(this.name,"-moz-crisp-edges"):super.old(e)}};i.names=["pixelated"],t.exports=i})),Sc=f(((e,t)=>{g();var r=ml(),n=class extends r{replace(e,t){let r=super.replace(e,t);return"-webkit-"===t&&(r=r.replace(/("[^"]+"|'[^']+')(\s+\d+\w)/gi,"url($1)$2")),r}};n.names=["image-set"],t.exports=n})),Cc=f(((e,t)=>{g();var r=xr().list,n=ml(),i=class extends n{replace(e,t){return r.space(e).map((e=>{if(e.slice(0,+this.name.length+1)!==this.name+"(")return e;let r=e.lastIndexOf(")"),n=e.slice(r+1),i=e.slice(this.name.length+1,r);if("-webkit-"===t){let e=i.match(/\d*.?\d+%?/);e?(i=i.slice(e[0].length).trim(),i+=`, ${e[0]}`):i+=", 0.5"}return t+this.name+"("+i+")"+n})).join(" ")}};i.names=["cross-fade"],t.exports=i})),Ac=f(((e,t)=>{g();var r=_l(),n=hl(),i=ml(),o=class extends i{constructor(e,t){super(e,t),"display-flex"===e&&(this.name="flex")}check(e){return"display"===e.prop&&e.value===this.name}prefixed(e){let t,n;return[t,e]=r(e),2009===t?n="flex"===this.name?"box":"inline-box":2012===t?n="flex"===this.name?"flexbox":"inline-flexbox":"final"===t&&(n=this.name),e+n}replace(e,t){return this.prefixed(t)}old(e){let t=this.prefixed(e);if(t)return new n(this.name,t)}};o.names=["display-flex","inline-flex"],t.exports=o})),Oc=f(((e,t)=>{g();var r=ml(),n=class extends r{constructor(e,t){super(e,t),"display-grid"===e&&(this.name="grid")}check(e){return"display"===e.prop&&e.value===this.name}};n.names=["display-grid","inline-grid"],t.exports=n})),Ec=f(((e,t)=>{g();var r=ml(),n=class extends r{constructor(e,t){super(e,t),"filter-function"===e&&(this.name="filter")}};n.names=["filter","filter-function"],t.exports=n})),_c=f(((e,t)=>{g();var r=nl(),n=ol(),i=al(),o=fl(),s=yl(),a=wl(),l=rl(),c=kl(),u=Sl(),d=ml(),p=tl(),f=Cl(),h=Al(),m=Ol(),y=El(),b=Tl(),v=Pl(),w=jl(),x=Il(),k=Dl(),S=$l(),C=Bl(),A=Rl(),O=Ml(),E=Ul(),_=zl(),T=Ll(),P=Fl(),j=Nl(),I=Vl(),D=Wl(),$=ql(),B=Gl(),R=Yl(),M=Hl(),U=Ql(),z=Jl(),L=Xl(),F=Kl(),N=Zl(),V=ec(),W=tc(),q=rc(),G=nc(),Y=ic(),H=oc(),Q=sc(),J=ac(),X=lc(),K=cc(),Z=uc(),ee=dc(),te=pc(),re=fc(),ne=hc(),ie=mc(),oe=gc(),se=yc(),ae=bc(),le=wc(),ce=xc(),ue=kc(),de=Sc(),pe=Cc(),fe=Ac(),he=Oc(),me=Ec();c.hack(f),c.hack(h),c.hack(m),c.hack(y),n.hack(b),n.hack(v),n.hack(w),n.hack(x),n.hack(k),n.hack(S),n.hack(C),n.hack(A),n.hack(O),n.hack(E),n.hack(_),n.hack(T),n.hack(P),n.hack(j),n.hack(I),n.hack(D),n.hack($),n.hack(B),n.hack(R),n.hack(M),n.hack(U),n.hack(z),n.hack(L),n.hack(F),n.hack(N),n.hack(V),n.hack(W),n.hack(q),n.hack(G),n.hack(Y),n.hack(H),n.hack(Q),n.hack(J),n.hack(X),n.hack(K),n.hack(Z),n.hack(ee),n.hack(te),n.hack(re),n.hack(ne),n.hack(ie),n.hack(oe),n.hack(se),n.hack(ae),d.hack(le),d.hack(ce),d.hack(ue),d.hack(de),d.hack(pe),d.hack(fe),d.hack(he),d.hack(me);var ge=new Map,ye=class{constructor(e,t,r={}){this.data=e,this.browsers=t,this.options=r,[this.add,this.remove]=this.preprocess(this.select(this.data)),this.transition=new o(this),this.processor=new s(this)}cleaner(){if(this.cleanerCache)return this.cleanerCache;if(!this.browsers.selected.length)return this;{let e=new l(this.browsers.data,[]);this.cleanerCache=new ye(this.data,e,this.options)}return this.cleanerCache}select(e){let t={add:{},remove:{}};for(let r in e){let n=e[r],i=n.browsers.map((e=>{let t=e.split(" ");return{browser:`${t[0]} ${t[1]}`,note:t[2]}})),o=i.filter((e=>e.note)).map((e=>`${this.browsers.prefix(e.browser)} ${e.note}`));o=p.uniq(o),i=i.filter((e=>this.browsers.isSelected(e.browser))).map((e=>{let t=this.browsers.prefix(e.browser);return e.note?`${t} ${e.note}`:t})),i=this.sort(p.uniq(i)),"no-2009"===this.options.flexbox&&(i=i.filter((e=>!e.includes("2009"))));let s=n.browsers.map((e=>this.browsers.prefix(e)));n.mistakes&&(s=s.concat(n.mistakes)),s=s.concat(o),s=p.uniq(s),i.length?(t.add[r]=i,i.length!i.includes(e))))):t.remove[r]=s}return t}sort(e){return e.sort(((e,t)=>{let r=p.removeNote(e).length,n=p.removeNote(t).length;return r===n?t.length-e.length:n-r}))}preprocess(e){let t={selectors:[],"@supports":new a(ye,this)};for(let r in e.add){let o=e.add[r];if("@keyframes"===r||"@viewport"===r)t[r]=new u(r,o,this);else if("@resolution"===r)t[r]=new i(r,o,this);else if(this.data[r].selector)t.selectors.push(c.load(r,o,this));else{let e=this.data[r].props;if(e){let n=d.load(r,o,this);for(let r of e)t[r]||(t[r]={values:[]}),t[r].values.push(n)}else{let e=t[r]&&t[r].values||[];t[r]=n.load(r,o,this),t[r].values=e}}}let r={selectors:[]};for(let n in e.remove){let o=e.remove[n];if(this.data[n].selector){let e=c.load(n,o);for(let t of o)r.selectors.push(e.old(t))}else if("@keyframes"===n||"@viewport"===n)for(let e of o)r[`@${e}${n.slice(1)}`]={remove:!0};else if("@resolution"===n)r[n]=new i(n,o,this);else{let e=this.data[n].props;if(e){let t=d.load(n,[],this);for(let n of o){let i=t.old(n);if(i)for(let t of e)r[t]||(r[t]={}),r[t].values||(r[t].values=[]),r[t].values.push(i)}}else for(let e of o){let i=this.decl(n).old(n,e);if("align-self"===n){let r=t[n]&&t[n].prefixes;if(r){if("-webkit- 2009"===e&&r.includes("-webkit-"))continue;if("-webkit-"===e&&r.includes("-webkit- 2009"))continue}}for(let e of i)r[e]||(r[e]={}),r[e].remove=!0}}}return[t,r]}decl(e){return ge.has(e)||ge.set(e,n.load(e)),ge.get(e)}unprefixed(e){let t=this.normalize(r.unprefixed(e));return"flex-direction"===t&&(t="flex-flow"),t}normalize(e){return this.decl(e).normalize(e)}prefixed(e,t){return e=r.unprefixed(e),this.decl(e).prefixed(e,t)}values(e,t){let r=this[e],n=r["*"]&&r["*"].values,i=r[t]&&r[t].values;return n&&i?p.uniq(n.concat(i)):n||i||[]}group(e){let t=e.parent,r=t.index(e),{length:n}=t.nodes,i=this.unprefixed(e.prop),o=(e,o)=>{for(r+=e;r>=0&&ro(-1,e),down:e=>o(1,e)}}};t.exports=ye})),Tc=f(((e,t)=>{g(),t.exports={"backdrop-filter":{feature:"css-backdrop-filter",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},element:{props:["background","background-image","border-image","mask","list-style","list-style-image","content","mask-image"],feature:"css-element-function",browsers:["firefox 114"]},"user-select":{mistakes:["-khtml-"],feature:"user-select-none",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},"background-clip":{feature:"background-clip-text",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},hyphens:{feature:"css-hyphens",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},fill:{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"fill-available":{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},stretch:{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["firefox 114"]},"fit-content":{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["firefox 114"]},"text-decoration-style":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-color":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-line":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-skip":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-skip-ink":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-size-adjust":{feature:"text-size-adjust",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"mask-clip":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-composite":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-image":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-origin":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-repeat":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-repeat":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-source":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},mask:{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-position":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-size":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-outset":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-width":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-slice":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"clip-path":{feature:"css-clip-path",browsers:["samsung 21"]},"box-decoration-break":{feature:"css-boxdecorationbreak",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","opera 99","safari 16.5","samsung 21"]},appearance:{feature:"css-appearance",browsers:["samsung 21"]},"image-set":{props:["background","background-image","border-image","cursor","mask","mask-image","list-style","list-style-image","content"],feature:"css-image-set",browsers:["and_uc 15.5","chrome 109","samsung 21"]},"cross-fade":{props:["background","background-image","border-image","mask","list-style","list-style-image","content","mask-image"],feature:"css-cross-fade",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},isolate:{props:["unicode-bidi"],feature:"css-unicode-bidi",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},"color-adjust":{feature:"css-color-adjust",browsers:["chrome 109","chrome 113","chrome 114","edge 114","opera 99"]}}})),Pc=f(((e,t)=>{g(),t.exports={}})),jc=f(((t,r)=>{g();var n=Qa(),{agents:i}=(Za(),Ja),o=el(),s=rl(),a=_c(),l=Tc(),c=Pc(),u={browsers:i,prefixes:l},d="\n Replace Autoprefixer `browsers` option to Browserslist config.\n Use `browserslist` key in `package.json` or `.browserslistrc` file.\n\n Using `browsers` option can cause errors. Browserslist config can\n be used for Babel, Autoprefixer, postcss-normalize and other tools.\n\n If you really need to use option, rename it to `overrideBrowserslist`.\n\n Learn more at:\n https://github.com/browserslist/browserslist#readme\n https://twitter.com/browserslist\n\n",p=new Map;function f(...t){let r;if(1===t.length&&function(e){return"[object Object]"===Object.prototype.toString.apply(e)}(t[0])?(r=t[0],t=void 0):0===t.length||1===t.length&&!t[0]?t=void 0:t.length<=2&&(Array.isArray(t[0])||!t[0])?(r=t[1],t=t[0]):"object"==typeof t[t.length-1]&&(r=t.pop()),r||(r={}),r.browser)throw new Error("Change `browser` option to `overrideBrowserslist` in Autoprefixer");if(r.browserslist)throw new Error("Change `browserslist` option to `overrideBrowserslist` in Autoprefixer");r.overrideBrowserslist?t=r.overrideBrowserslist:r.browsers&&("undefined"!=typeof console&&console.warn&&(o.red?console.warn(o.red(d.replace(/`[^`]+`/g,(e=>o.yellow(e.slice(1,-1)))))):console.warn(d)),t=r.browsers);let n={ignoreUnknownVersions:r.ignoreUnknownVersions,stats:r.stats,env:r.env};function i(e){let i=u,o=new s(i.browsers,t,e,n),l=o.selected.join(", ")+JSON.stringify(r);return p.has(l)||p.set(l,new a(i.prefixes,o,r)),p.get(l)}return{postcssPlugin:"autoprefixer",prepare(e){let t=i({from:e.opts.from,env:r.env});return{OnceExit(n){(function(e,t){0!==t.browsers.selected.length&&(t.add.selectors.length>0||Object.keys(t.add).length>2||e.warn("Autoprefixer target browsers do not need any prefixes.You do not need Autoprefixer anymore.\nCheck your Browserslist config to be sure that your targets are set up correctly.\n\n Learn more at:\n https://github.com/postcss/autoprefixer#readme\n https://github.com/browserslist/browserslist#readme\n\n"))})(e,t),!1!==r.remove&&t.processor.remove(n,e),!1!==r.add&&t.processor.add(n,e)}}},info:t=>((t=t||{}).from=t.from||e.cwd(),c(i(t))),options:r,browsers:t}}r.exports=f,f.postcss=!0,f.data=u,f.defaults=n.defaults,f.info=()=>f().info()})),Ic={};h(Ic,{default:()=>Dc});var Dc,$c=p((()=>{g(),Dc=[]})),Bc={};h(Bc,{default:()=>Mc});var Rc,Mc,Uc=p((()=>{g(),K(),Rc=m(Et()),Mc=X(Rc.default.theme)})),zc={};h(zc,{default:()=>Fc});var Lc,Fc,Nc=p((()=>{g(),K(),Lc=m(Et()),Fc=X(Lc.default)}));g();var Vc=Kc(Ha()),Wc=Kc(xr()),qc=Kc(jc()),Gc=Kc(($c(),Ic)),Yc=Kc((Uc(),Bc)),Hc=Kc((Nc(),zc)),Qc=Kc((U(),B)),Jc=Kc((Jo(),Ho)),Xc=Kc((Dt(),jt));function Kc(e){return e&&e.__esModule?e:{default:e}}console.warn("cdn.tailwindcss.com should not be used in production. To use Tailwind CSS in production, install it as a PostCSS plugin or use the Tailwind CLI: https://tailwindcss.com/docs/installation");var Zc,eu,tu="tailwind",ru="text/tailwindcss",nu="/template.html",iu=!0,ou=0,su=new Set,au="",lu=(e=!1)=>({get:(t,r)=>e&&"config"!==r||"object"!=typeof t[r]||null===t[r]?t[r]:new Proxy(t[r],lu()),set:(t,r,n)=>(t[r]=n,(!e||"config"===r)&&uu(!0),!0)});function cu(e){eu.observe(e,{attributes:!0,attributeFilter:["type"],characterData:!0,subtree:!0,childList:!0})}async function uu(e=!1){e&&(ou++,su.clear());let t="";for(let e of document.querySelectorAll(`style[type="${ru}"]`))t+=e.textContent;let r=new Set;for(let e of document.querySelectorAll("[class]"))for(let t of e.classList)su.has(t)||r.add(t);if(document.body&&(iu||r.size>0||t!==au||!Zc||!Zc.isConnected)){for(let e of r)su.add(e);iu=!1,au=t,self[nu]=Array.from(r).join(" ");let{css:e}=await(0,Wc.default)([(0,Vc.default)({...window[tu].config,_hash:ou,content:{files:[nu],extract:{html:e=>e.split(" ")}},plugins:[...Gc.default,...Array.isArray(window[tu].config.plugins)?window[tu].config.plugins:[]]}),(0,qc.default)({remove:!1})]).process(`@tailwind base;@tailwind components;@tailwind utilities;${t}`);(!Zc||!Zc.isConnected)&&(Zc=document.createElement("style"),document.head.append(Zc)),Zc.textContent=e}}window[tu]=new Proxy({config:{},defaultTheme:Yc.default,defaultConfig:Hc.default,colors:Qc.default,plugin:Jc.default,resolveConfig:Xc.default},lu(!0)),new MutationObserver((async e=>{let t=!1;if(!eu){eu=new MutationObserver((async()=>await uu(!0)));for(let e of document.querySelectorAll(`style[type="${ru}"]`))cu(e)}for(let r of e)for(let e of r.addedNodes)1===e.nodeType&&"STYLE"===e.tagName&&e.getAttribute("type")===ru&&(cu(e),t=!0);await uu(t)})).observe(document.documentElement,{attributes:!0,attributeFilter:["class"],childList:!0,subtree:!0})})(); \ No newline at end of file diff --git a/App/FeatureSet/PublicDashboard/src/App.tsx b/App/FeatureSet/PublicDashboard/src/App.tsx new file mode 100644 index 0000000000..d834ceec92 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/App.tsx @@ -0,0 +1,229 @@ +import PageMap from "./Utils/PageMap"; +import RouteMap from "./Utils/RouteMap"; +import RouteParams from "./Utils/RouteParams"; +import PublicDashboardUtil from "./Utils/PublicDashboard"; +import { PUBLIC_DASHBOARD_API_URL } from "./Utils/Config"; +import API from "./Utils/API"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PageLoader from "Common/UI/Components/Loader/PageLoader"; +import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage"; +import Navigation from "Common/UI/Utils/Navigation"; +import React, { lazy, Suspense, useEffect, useState } from "react"; +import { + Route as PageRoute, + Routes, + useLocation, + useNavigate, + useParams, +} from "react-router-dom"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import useAsyncEffect from "use-async-effect"; + +// Lazy load page components +type AllPagesModule = typeof import("./Pages/AllPages"); + +const DashboardViewPage: React.LazyExoticComponent< + AllPagesModule["DashboardViewPage"] +> = lazy(() => { + return import("./Pages/AllPages").then((m: AllPagesModule) => { + return { default: m.DashboardViewPage }; + }); +}); + +const MasterPassword: React.LazyExoticComponent< + AllPagesModule["MasterPassword"] +> = lazy(() => { + return import("./Pages/AllPages").then((m: AllPagesModule) => { + return { default: m.MasterPassword }; + }); +}); + +const NotFoundPage: React.LazyExoticComponent< + AllPagesModule["NotFoundPage"] +> = lazy(() => { + return import("./Pages/AllPages").then((m: AllPagesModule) => { + return { default: m.NotFoundPage }; + }); +}); + +const ForbiddenPage: React.LazyExoticComponent< + AllPagesModule["ForbiddenPage"] +> = lazy(() => { + return import("./Pages/AllPages").then((m: AllPagesModule) => { + return { default: m.ForbiddenPage }; + }); +}); + +const App: () => JSX.Element = () => { + Navigation.setNavigateHook(useNavigate()); + Navigation.setLocation(useLocation()); + Navigation.setParams(useParams()); + + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [dashboardId, setDashboardId] = useState(null); + const [dashboardName, setDashboardName] = useState("Dashboard"); + const [isPreview, setIsPreview] = useState(false); + + type GetIdFunction = () => Promise; + + const getId: GetIdFunction = async (): Promise => { + if (PublicDashboardUtil.isPreviewPage()) { + const id: string | null = Navigation.getParamByName( + RouteParams.DashboardId, + RouteMap[PageMap.PREVIEW_OVERVIEW]!, + ); + if (id) { + return new ObjectID(id); + } + } + + // Get dashboard ID by hostname (custom domain) + const response: HTTPResponse = await API.post({ + url: URL.fromString(PUBLIC_DASHBOARD_API_URL.toString()).addRoute( + `/domain`, + ), + data: { + domain: Navigation.getHostname().toString(), + }, + headers: {}, + }); + + if (response.data && response.data["dashboardId"]) { + return new ObjectID(response.data["dashboardId"] as string); + } + + throw new BadDataException("Dashboard not found for this domain"); + }; + + useEffect(() => { + const preview: boolean = PublicDashboardUtil.isPreviewPage(); + setIsPreview(preview); + }, []); + + useAsyncEffect(async () => { + try { + setIsLoading(true); + + const id: ObjectID = await getId(); + setDashboardId(id); + PublicDashboardUtil.setDashboardId(id); + + // Fetch dashboard metadata + const response: HTTPResponse = await API.post({ + url: URL.fromString(PUBLIC_DASHBOARD_API_URL.toString()).addRoute( + `/metadata/${id.toString()}`, + ), + data: {}, + headers: {}, + }); + + if (response.data) { + const name: string = + (response.data["name"] as string) || "Dashboard"; + setDashboardName(name); + document.title = name; + + const enableMasterPassword: boolean = Boolean( + response.data["enableMasterPassword"], + ); + const isPublicDashboard: boolean = Boolean( + response.data["isPublicDashboard"], + ); + + if (!isPublicDashboard && enableMasterPassword) { + PublicDashboardUtil.setRequiresMasterPassword(true); + } else { + PublicDashboardUtil.setRequiresMasterPassword(false); + } + } + + setIsLoading(false); + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); + + if (isLoading) { + return ; + } + + if (error) { + return ( +
+ +
+ ); + } + + // Check if master password is required and not validated + if ( + dashboardId && + PublicDashboardUtil.requiresMasterPassword() && + !PublicDashboardUtil.isMasterPasswordValidated() && + !Navigation.getCurrentRoute().toString().includes("master-password") + ) { + PublicDashboardUtil.navigateToMasterPasswordPage(); + return ; + } + + return ( + }> + + {/* Live routes (custom domain) */} + + ) : ( + + ) + } + /> + + } + /> + + } + /> + + {/* Preview routes (via /public-dashboard/:dashboardId) */} + + ) : ( + + ) + } + /> + + } + /> + + } + /> + + {/* Catch-all */} + } /> + + + ); +}; + +export default App; diff --git a/App/FeatureSet/PublicDashboard/src/Components/DashboardCanvas.tsx b/App/FeatureSet/PublicDashboard/src/Components/DashboardCanvas.tsx new file mode 100644 index 0000000000..1b4c718e0f --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Components/DashboardCanvas.tsx @@ -0,0 +1,6 @@ +// Re-export the DashboardCanvas from the Dashboard FeatureSet +// The PublicDashboard app reuses the same canvas rendering logic +export { + default, + type ComponentProps, +} from "../../../Dashboard/src/Components/Dashboard/Canvas/Index"; diff --git a/App/FeatureSet/PublicDashboard/src/Index.tsx b/App/FeatureSet/PublicDashboard/src/Index.tsx new file mode 100644 index 0000000000..8f57fca1b8 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Index.tsx @@ -0,0 +1,19 @@ +import App from "./App"; +import Telemetry from "Common/UI/Utils/Telemetry/Telemetry"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; + +Telemetry.init({ + serviceName: "public-dashboard", +}); + +const root: any = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement, +); + +root.render( + + + , +); diff --git a/App/FeatureSet/PublicDashboard/src/Pages/AllPages.tsx b/App/FeatureSet/PublicDashboard/src/Pages/AllPages.tsx new file mode 100644 index 0000000000..8a6f48ea52 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Pages/AllPages.tsx @@ -0,0 +1,4 @@ +export { default as DashboardViewPage } from "./DashboardView/DashboardViewPage"; +export { default as MasterPassword } from "./MasterPassword/MasterPassword"; +export { default as NotFoundPage } from "./NotFound/NotFound"; +export { default as ForbiddenPage } from "./Forbidden/Forbidden"; diff --git a/App/FeatureSet/PublicDashboard/src/Pages/DashboardView/DashboardVariableSelector.tsx b/App/FeatureSet/PublicDashboard/src/Pages/DashboardView/DashboardVariableSelector.tsx new file mode 100644 index 0000000000..f5b5a3753a --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Pages/DashboardView/DashboardVariableSelector.tsx @@ -0,0 +1,53 @@ +import React, { FunctionComponent, ReactElement } from "react"; +import DashboardVariable from "Common/Types/Dashboard/DashboardVariable"; + +export interface ComponentProps { + variables: Array; + onVariableValueChange: (variableId: string, value: string) => void; +} + +const DashboardVariableSelector: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + return ( +
+ {props.variables.map((variable: DashboardVariable) => { + return ( +
+ + {variable.options && variable.options.length > 0 ? ( + + ) : ( + ) => { + props.onVariableValueChange(variable.id!, e.target.value); + }} + /> + )} +
+ ); + })} +
+ ); +}; + +export default DashboardVariableSelector; diff --git a/App/FeatureSet/PublicDashboard/src/Pages/DashboardView/DashboardViewPage.tsx b/App/FeatureSet/PublicDashboard/src/Pages/DashboardView/DashboardViewPage.tsx new file mode 100644 index 0000000000..b7c428f9b7 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Pages/DashboardView/DashboardViewPage.tsx @@ -0,0 +1,422 @@ +import React, { + FunctionComponent, + ReactElement, + useCallback, + useEffect, + useRef, + useState, +} from "react"; +import DashboardCanvas from "../../Components/DashboardCanvas"; +import DashboardMode from "Common/Types/Dashboard/DashboardMode"; +import DashboardViewConfig, { + AutoRefreshInterval, + getAutoRefreshIntervalInMs, + getAutoRefreshIntervalLabel, +} from "Common/Types/Dashboard/DashboardViewConfig"; +import { ObjectType } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Dashboard from "Common/Models/DatabaseModels/Dashboard"; +import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI"; +import API from "../../Utils/API"; +import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "Common/UI/Components/Loader/PageLoader"; +import DashboardViewConfigUtil from "Common/Utils/Dashboard/DashboardViewConfig"; +import DefaultDashboardSize from "Common/Types/Dashboard/DashboardSize"; +import { PromiseVoidFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import RangeStartAndEndDateTime from "Common/Types/Time/RangeStartAndEndDateTime"; +import TimeRange from "Common/Types/Time/TimeRange"; +import DashboardVariable from "Common/Types/Dashboard/DashboardVariable"; +import RangeStartAndEndDateView from "Common/UI/Components/Date/RangeStartAndEndDateView"; +import MoreMenu from "Common/UI/Components/MoreMenu/MoreMenu"; +import MoreMenuItem from "Common/UI/Components/MoreMenu/MoreMenuItem"; +import IconProp from "Common/Types/Icon/IconProp"; +import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button"; +import DashboardVariableSelector from "./DashboardVariableSelector"; +import DashboardBaseComponent from "Common/Types/Dashboard/DashboardComponents/DashboardBaseComponent"; +import NavBar from "Common/UI/Components/Navbar/NavBar"; +import NavBarItem from "Common/UI/Components/Navbar/NavBarItem"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PublicDashboardUtil from "../../Utils/PublicDashboard"; +import Route from "Common/Types/API/Route"; + +export interface ComponentProps { + dashboardId: ObjectID; + onLoadComplete?: (() => void) | undefined; +} + +const DashboardViewPage: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + const [startAndEndDate, setStartAndEndDate] = + useState({ + range: TimeRange.PAST_ONE_HOUR, + }); + + const [autoRefreshInterval, setAutoRefreshInterval] = + useState(AutoRefreshInterval.OFF); + const [isRefreshing, setIsRefreshing] = useState(false); + const [dashboardVariables, setDashboardVariables] = useState< + Array + >([]); + const [timeRangeStack, setTimeRangeStack] = useState< + Array + >([]); + const autoRefreshTimerRef: React.MutableRefObject | null> = useRef | null>(null); + const [refreshTick, setRefreshTick] = useState(0); + + const dashboardViewRef: React.RefObject = + useRef(null); + + const [dashboardTotalWidth, setDashboardTotalWidth] = useState(0); + const [dashboardName, setDashboardName] = useState(""); + + const handleResize: VoidFunction = (): void => { + setDashboardTotalWidth(dashboardViewRef.current?.offsetWidth || 0); + }; + + useEffect(() => { + setDashboardTotalWidth(dashboardViewRef.current?.offsetWidth || 0); + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + const [dashboardViewConfig, setDashboardViewConfig] = + useState({ + _type: ObjectType.DashboardViewConfig, + components: [], + heightInDashboardUnits: DefaultDashboardSize.heightInDashboardUnits, + }); + + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const hasComponents: boolean = !!( + dashboardViewConfig && + dashboardViewConfig.components && + dashboardViewConfig.components.length > 0 + ); + + const fetchDashboardViewConfig: PromiseVoidFunction = + async (): Promise => { + const dashboard: Dashboard | null = await ModelAPI.getItem({ + modelType: Dashboard, + id: props.dashboardId, + select: { + dashboardViewConfig: true, + name: true, + description: true, + }, + }); + + if (!dashboard) { + setError("Dashboard not found"); + return; + } + + const config: DashboardViewConfig = JSONFunctions.deserializeValue( + dashboard.dashboardViewConfig || + DashboardViewConfigUtil.createDefaultDashboardViewConfig(), + ) as DashboardViewConfig; + + setDashboardViewConfig(config); + setDashboardName(dashboard.name || "Untitled Dashboard"); + + if (config.refreshInterval) { + setAutoRefreshInterval(config.refreshInterval); + } + + if (config.variables) { + setDashboardVariables(config.variables); + } + }; + + const loadPage: PromiseVoidFunction = async (): Promise => { + setIsLoading(true); + setError(null); + try { + await fetchDashboardViewConfig(); + } catch (err) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + props.onLoadComplete?.(); + }; + + useEffect(() => { + loadPage().catch((err: Error) => { + setError(API.getFriendlyErrorMessage(err as Error)); + }); + }, []); + + // Auto-refresh + const triggerRefresh: () => void = useCallback(() => { + setIsRefreshing(true); + setRefreshTick((prev: number) => prev + 1); + setTimeout(() => { + setIsRefreshing(false); + }, 500); + }, []); + + useEffect(() => { + if (autoRefreshTimerRef.current) { + clearInterval(autoRefreshTimerRef.current); + autoRefreshTimerRef.current = null; + } + + const intervalMs: number | null = + getAutoRefreshIntervalInMs(autoRefreshInterval); + + if (intervalMs !== null) { + autoRefreshTimerRef.current = setInterval(() => { + triggerRefresh(); + }, intervalMs); + } + + return () => { + if (autoRefreshTimerRef.current) { + clearInterval(autoRefreshTimerRef.current); + autoRefreshTimerRef.current = null; + } + }; + }, [autoRefreshInterval, triggerRefresh]); + + const dashboardCanvasRef: React.RefObject = + useRef(null); + + if (error) { + return ; + } + + if (isLoading) { + return ; + } + + const isPreview: boolean = PublicDashboardUtil.isPreviewPage(); + + const overviewRoute: Route = RouteUtil.populateRouteParams( + isPreview + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ); + + return ( +
+ {/* Header and NavBar */} +
+
+

+ {dashboardName} +

+
+ + + + +
+ + {/* Public Dashboard Toolbar */} +
+
+
+
+ {hasComponents && ( + + {dashboardViewConfig.components.length} widget + {dashboardViewConfig.components.length !== 1 ? "s" : ""} + + )} + {isRefreshing && + autoRefreshInterval !== AutoRefreshInterval.OFF && ( + + + Refreshing + + )} +
+ +
+ {/* Reset Zoom button */} + {timeRangeStack.length > 0 && ( +
+
+ + {/* Bottom row: Time range + variables */} + {hasComponents && ( +
+
+ { + setTimeRangeStack([ + ...timeRangeStack, + startAndEndDate, + ]); + setStartAndEndDate(newRange); + }} + /> +
+ + {dashboardVariables.length > 0 && ( + <> +
+ { + setDashboardVariables( + dashboardVariables.map( + (v: DashboardVariable) => { + if (v.id === variableId) { + return { ...v, currentValue: value }; + } + return v; + }, + ), + ); + }} + /> + + )} +
+ )} +
+ +
+ { + // Read-only in public view + }} + dashboardMode={DashboardMode.View} + selectedComponentId={null} + onComponentSelected={(_id: ObjectID | null) => { + // No selection in public view + }} + dashboardTotalWidth={dashboardTotalWidth} + startAndEndDate={startAndEndDate} + onStartAndEndDateChange={( + newRange: RangeStartAndEndDateTime, + ) => { + setTimeRangeStack([...timeRangeStack, startAndEndDate]); + setStartAndEndDate(newRange); + }} + refreshTick={refreshTick} + dashboardVariables={dashboardVariables} + /> +
+ + {/* Footer */} +
+
+ Powered by + + OneUptime + +
+
+
+ ); +}; + +export default DashboardViewPage; diff --git a/App/FeatureSet/PublicDashboard/src/Pages/Forbidden/Forbidden.tsx b/App/FeatureSet/PublicDashboard/src/Pages/Forbidden/Forbidden.tsx new file mode 100644 index 0000000000..0db99f00b8 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Pages/Forbidden/Forbidden.tsx @@ -0,0 +1,20 @@ +import React, { FunctionComponent, ReactElement } from "react"; + +const ForbiddenPage: FunctionComponent = (): ReactElement => { + return ( +
+
+

403

+

+ Access Denied +

+

+ You do not have permission to view this dashboard. Your IP address may + be restricted. +

+
+
+ ); +}; + +export default ForbiddenPage; diff --git a/App/FeatureSet/PublicDashboard/src/Pages/MasterPassword/MasterPassword.tsx b/App/FeatureSet/PublicDashboard/src/Pages/MasterPassword/MasterPassword.tsx new file mode 100644 index 0000000000..45ea1f8039 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Pages/MasterPassword/MasterPassword.tsx @@ -0,0 +1,167 @@ +import { PUBLIC_DASHBOARD_API_URL } from "../../Utils/Config"; +import PublicDashboardUtil from "../../Utils/PublicDashboard"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import BasicForm from "Common/UI/Components/Forms/BasicForm"; +import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType"; +import { JSONObject } from "Common/Types/JSON"; +import API from "../../Utils/API"; +import Navigation from "Common/UI/Utils/Navigation"; +import PageLoader from "Common/UI/Components/Loader/PageLoader"; +import React, { FunctionComponent, useEffect, useState } from "react"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; + +export interface ComponentProps { + dashboardName: string; +} + +const MasterPasswordPage: FunctionComponent = ( + props: ComponentProps, +): JSX.Element => { + const [isSubmitting, setIsSubmitting] = useState(false); + const [formError, setFormError] = useState(null); + + const dashboardId: ObjectID | null = PublicDashboardUtil.getDashboardId(); + + const redirectToOverview: () => void = (): void => { + const path: string = PublicDashboardUtil.isPreviewPage() + ? `/public-dashboard/${PublicDashboardUtil.getDashboardId()?.toString()}` + : "/"; + + Navigation.navigate(new Route(path), { forceNavigate: true }); + }; + + useEffect(() => { + if (!PublicDashboardUtil.requiresMasterPassword()) { + redirectToOverview(); + return; + } + + if (PublicDashboardUtil.isMasterPasswordValidated()) { + redirectToOverview(); + return; + } + }, [dashboardId]); + + if (!dashboardId || !PublicDashboardUtil.requiresMasterPassword()) { + return ; + } + + const handleFormSubmit: ( + values: JSONObject, + onSubmitSuccessful?: () => void, + ) => Promise = async ( + values: JSONObject, + onSubmitSuccessful?: () => void, + ): Promise => { + const submittedPassword: string = + (values["password"] as { toString: () => string } | undefined) + ?.toString() + .trim() || ""; + + if (!submittedPassword) { + setFormError("Password is required."); + return; + } + + if (!dashboardId) { + throw new BadDataException("Dashboard ID not found"); + } + + const url: URL = URL.fromString( + PUBLIC_DASHBOARD_API_URL.toString(), + ).addRoute(`/master-password/${dashboardId.toString()}`); + + setIsSubmitting(true); + setFormError(null); + + try { + const response: HTTPResponse | HTTPErrorResponse = + await API.post({ + url, + data: { + password: submittedPassword, + }, + }); + + if (response.isFailure()) { + throw response; + } + + PublicDashboardUtil.setMasterPasswordValidated(true); + + const redirectUrl: string | null = + Navigation.getQueryStringByName("redirectUrl"); + + if (redirectUrl) { + Navigation.navigate(new Route(redirectUrl), { + forceNavigate: true, + }); + } else { + redirectToOverview(); + } + + onSubmitSuccessful?.(); + } catch (err) { + setFormError(API.getFriendlyMessage(err)); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+
+

+ {props.dashboardName + ? `Enter ${props.dashboardName} Password` + : "Enter Password"} +

+

+ Please enter the password to view this dashboard. +

+
+ +
+
+ void, + ) => { + void handleFormSubmit(values, onSubmitSuccessful); + }} + footer={<>} + /> +
+
+
+ ); +}; + +export default MasterPasswordPage; diff --git a/App/FeatureSet/PublicDashboard/src/Pages/NotFound/NotFound.tsx b/App/FeatureSet/PublicDashboard/src/Pages/NotFound/NotFound.tsx new file mode 100644 index 0000000000..bccc420b6b --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Pages/NotFound/NotFound.tsx @@ -0,0 +1,20 @@ +import React, { FunctionComponent, ReactElement } from "react"; + +const NotFoundPage: FunctionComponent = (): ReactElement => { + return ( +
+
+

404

+

+ Dashboard Not Found +

+

+ The dashboard you are looking for does not exist or is no longer + available. +

+
+
+ ); +}; + +export default NotFoundPage; diff --git a/App/FeatureSet/PublicDashboard/src/Server/Utils/PublicDashboard.ts b/App/FeatureSet/PublicDashboard/src/Server/Utils/PublicDashboard.ts new file mode 100644 index 0000000000..40e2b95574 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Server/Utils/PublicDashboard.ts @@ -0,0 +1,98 @@ +import { ExpressRequest } from "Common/Server/Utils/Express"; +import API from "Common/Utils/API"; +import URL from "Common/Types/API/URL"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import { JSONObject } from "Common/Types/JSON"; +import logger from "Common/Server/Utils/Logger"; + +const DashboardApiInternalUrl: string = + process.env["DASHBOARD_API_URL"] || + `http://${process.env["SERVER_APP_HOSTNAME"] || "localhost"}:${process.env["APP_PORT"] || "3002"}/api/dashboard`; + +export interface PublicDashboardData { + id: string; + title: string; + description: string; +} + +export const getPublicDashboardData: ( + req: ExpressRequest, +) => Promise = async ( + req: ExpressRequest, +): Promise => { + try { + logger.debug("Getting public dashboard data"); + + let dashboardIdOrDomain: string = ""; + let isPreview: boolean = false; + + const path: string = req.path; + logger.debug(`Request path: ${path}`); + + if (path && path.includes("/public-dashboard/")) { + dashboardIdOrDomain = + path.split("/public-dashboard/")[1]?.split("/")[0] || ""; + isPreview = true; + logger.debug(`Found dashboard ID in URL: ${dashboardIdOrDomain}`); + } else { + const host: string = + req.hostname?.toString() || req.headers["host"]?.toString() || ""; + if (host) { + dashboardIdOrDomain = host; + logger.debug( + `Found domain in request headers: ${dashboardIdOrDomain}`, + ); + } + } + + if (!dashboardIdOrDomain) { + logger.debug("No dashboard ID or domain found"); + return null; + } + + let dashboardId: string; + let title: string = "Dashboard"; + let description: string = "View dashboard metrics and insights."; + + if (isPreview) { + dashboardId = dashboardIdOrDomain; + } else { + logger.debug( + `Pinging the API with dashboardIdOrDomain: ${dashboardIdOrDomain}`, + ); + const response: HTTPErrorResponse | HTTPResponse = + await API.get({ + url: URL.fromString(DashboardApiInternalUrl).addRoute( + `/seo/${dashboardIdOrDomain}`, + ), + }); + + if (response instanceof HTTPErrorResponse) { + logger.debug(`Received error response from API: ${response}`); + return null; + } + + logger.debug("Successfully received response from API"); + + dashboardId = response.data?.["_id"] as string; + if (!dashboardId) { + logger.debug("No dashboard ID in response"); + return null; + } + + title = (response.data?.["title"] as string) || title; + description = (response.data?.["description"] as string) || description; + } + + return { + id: dashboardId, + title, + description, + }; + } catch (err) { + logger.error("Error getting public dashboard data:"); + logger.error(err); + return null; + } +}; diff --git a/App/FeatureSet/PublicDashboard/src/Utils/API.ts b/App/FeatureSet/PublicDashboard/src/Utils/API.ts new file mode 100644 index 0000000000..0f4ad8ee09 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Utils/API.ts @@ -0,0 +1,48 @@ +import PublicDashboardUtil from "./PublicDashboard"; +import Headers from "Common/Types/API/Headers"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import BaseAPI from "Common/UI/Utils/API/API"; + +export default class API extends BaseAPI { + public static override getDefaultHeaders(): Headers { + const dashboardId: ObjectID | null = + PublicDashboardUtil.getDashboardId(); + + if (!dashboardId) { + return {}; + } + + return { + "dashboard-id": dashboardId.toString(), + tenantid: "", + }; + } + + public static override getLoginRoute(): Route { + const basePath: string = PublicDashboardUtil.isPreviewPage() + ? `/public-dashboard/${PublicDashboardUtil.getDashboardId()?.toString()}` + : ""; + + if ( + PublicDashboardUtil.requiresMasterPassword() && + !PublicDashboardUtil.isMasterPasswordValidated() + ) { + return new Route(`${basePath}/master-password`); + } + + return new Route(`${basePath}/`); + } + + public static override logoutUser(): void { + // No-op for public dashboards + } + + public static override getForbiddenRoute(): Route { + return new Route( + PublicDashboardUtil.isPreviewPage() + ? `/public-dashboard/${PublicDashboardUtil.getDashboardId()?.toString()}/forbidden` + : "/forbidden", + ); + } +} diff --git a/App/FeatureSet/PublicDashboard/src/Utils/Config.ts b/App/FeatureSet/PublicDashboard/src/Utils/Config.ts new file mode 100644 index 0000000000..f8d1cf451e --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Utils/Config.ts @@ -0,0 +1,13 @@ +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; + +const PROTOCOL: Protocol = window.location.protocol.includes("https") + ? Protocol.HTTPS + : Protocol.HTTP; + +export const PUBLIC_DASHBOARD_API_URL: URL = new URL( + PROTOCOL, + window.location.host, + new Route("/public-dashboard-api"), +); diff --git a/App/FeatureSet/PublicDashboard/src/Utils/PageMap.ts b/App/FeatureSet/PublicDashboard/src/Utils/PageMap.ts new file mode 100644 index 0000000000..c3a7d17016 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Utils/PageMap.ts @@ -0,0 +1,13 @@ +enum PageMap { + OVERVIEW = "OVERVIEW", + NOT_FOUND = "NOT_FOUND", + FORBIDDEN = "FORBIDDEN", + MASTER_PASSWORD = "MASTER_PASSWORD", + + // Preview routes (accessed via /public-dashboard/:dashboardId) + PREVIEW_OVERVIEW = "PREVIEW_OVERVIEW", + PREVIEW_FORBIDDEN = "PREVIEW_FORBIDDEN", + PREVIEW_MASTER_PASSWORD = "PREVIEW_MASTER_PASSWORD", +} + +export default PageMap; diff --git a/App/FeatureSet/PublicDashboard/src/Utils/PublicDashboard.ts b/App/FeatureSet/PublicDashboard/src/Utils/PublicDashboard.ts new file mode 100644 index 0000000000..d13db424a3 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Utils/PublicDashboard.ts @@ -0,0 +1,113 @@ +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import Typeof from "Common/Types/Typeof"; +import LocalStorage from "Common/UI/Utils/LocalStorage"; +import Navigation from "Common/UI/Utils/Navigation"; + +export default class PublicDashboardUtil { + public static getDashboardId(): ObjectID | null { + const value: ObjectID | null = LocalStorage.getItem( + "dashboardId", + ) as ObjectID | null; + + if (value && typeof value === Typeof.String) { + return new ObjectID(value.toString()); + } + + return value; + } + + public static setDashboardId(id: ObjectID | null): void { + LocalStorage.setItem("dashboardId", id); + } + + public static setRequiresMasterPassword(value: boolean): void { + const storageKey: string = PublicDashboardUtil.getRequiresMasterPasswordStorageKey(); + LocalStorage.setItem(storageKey, value); + + if (!value) { + PublicDashboardUtil.setMasterPasswordValidated(false); + } + } + + public static requiresMasterPassword(): boolean { + const storageKey: string = PublicDashboardUtil.getRequiresMasterPasswordStorageKey(); + return Boolean(LocalStorage.getItem(storageKey)); + } + + private static getDashboardScopedStorageKey(baseKey: string): string { + const dashboardId: ObjectID | null = PublicDashboardUtil.getDashboardId(); + + if (!dashboardId) { + return baseKey; + } + + return `${baseKey}-${dashboardId.toString()}`; + } + + private static getRequiresMasterPasswordStorageKey(): string { + return PublicDashboardUtil.getDashboardScopedStorageKey( + "dashboardRequiresMasterPassword", + ); + } + + private static getMasterPasswordValidationStorageKey(): string { + const dashboardId: ObjectID | null = PublicDashboardUtil.getDashboardId(); + + if (!dashboardId) { + return "dashboardMasterPasswordValidated"; + } + + return `dashboardMasterPasswordValidated-${dashboardId.toString()}`; + } + + public static setMasterPasswordValidated(value: boolean): void { + const storageKey: string = + PublicDashboardUtil.getMasterPasswordValidationStorageKey(); + LocalStorage.setItem(storageKey, value); + } + + public static isMasterPasswordValidated(): boolean { + const storageKey: string = + PublicDashboardUtil.getMasterPasswordValidationStorageKey(); + return Boolean(LocalStorage.getItem(storageKey)); + } + + public static isPreviewPage(): boolean { + return Navigation.containsInPath("/public-dashboard/"); + } + + public static navigateToMasterPasswordPage(): void { + if (Navigation.getCurrentRoute().toString().includes("master-password")) { + return; + } + + const currentPath: string = Navigation.getCurrentPath().toString(); + const basePath: string = PublicDashboardUtil.isPreviewPage() + ? `/public-dashboard/${PublicDashboardUtil.getDashboardId()?.toString()}` + : ""; + + const route: Route = new Route( + `${basePath}/master-password?redirectUrl=${currentPath}`, + ); + + Navigation.navigate(route, { forceNavigate: true }); + } + + public static async checkIfTheUserIsAuthenticated( + errorResponse: HTTPErrorResponse, + ): Promise { + if ( + errorResponse instanceof HTTPErrorResponse && + errorResponse.statusCode === 401 + ) { + if ( + PublicDashboardUtil.requiresMasterPassword() && + !PublicDashboardUtil.isMasterPasswordValidated() + ) { + PublicDashboardUtil.navigateToMasterPasswordPage(); + } + } + } +} diff --git a/App/FeatureSet/PublicDashboard/src/Utils/RouteMap.ts b/App/FeatureSet/PublicDashboard/src/Utils/RouteMap.ts new file mode 100644 index 0000000000..60fa962d60 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Utils/RouteMap.ts @@ -0,0 +1,47 @@ +import PageMap from "./PageMap"; +import RouteParams from "./RouteParams"; +import Route from "Common/Types/API/Route"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import LocalStorage from "Common/UI/Utils/LocalStorage"; + +const RouteMap: Dictionary = { + [PageMap.OVERVIEW]: new Route(`/`), + [PageMap.NOT_FOUND]: new Route(`/not-found`), + [PageMap.FORBIDDEN]: new Route(`/forbidden`), + [PageMap.MASTER_PASSWORD]: new Route(`/master-password`), + + // Preview routes + [PageMap.PREVIEW_OVERVIEW]: new Route( + `/public-dashboard/${RouteParams.DashboardId}`, + ), + [PageMap.PREVIEW_FORBIDDEN]: new Route( + `/public-dashboard/${RouteParams.DashboardId}/forbidden`, + ), + [PageMap.PREVIEW_MASTER_PASSWORD]: new Route( + `/public-dashboard/${RouteParams.DashboardId}/master-password`, + ), +}; + +export class RouteUtil { + public static populateRouteParams(route: Route, modelId?: ObjectID): Route { + const tempRoute: Route = new Route(route.toString()); + + if (modelId) { + route = tempRoute.addRouteParam(RouteParams.ModelID, modelId.toString()); + } + + const id: ObjectID = LocalStorage.getItem("dashboardId") as ObjectID; + + if (id) { + route = tempRoute.addRouteParam( + RouteParams.DashboardId, + id.toString(), + ); + } + + return tempRoute; + } +} + +export default RouteMap; diff --git a/App/FeatureSet/PublicDashboard/src/Utils/RouteParams.ts b/App/FeatureSet/PublicDashboard/src/Utils/RouteParams.ts new file mode 100644 index 0000000000..90b24ee73f --- /dev/null +++ b/App/FeatureSet/PublicDashboard/src/Utils/RouteParams.ts @@ -0,0 +1,6 @@ +enum RouteParams { + DashboardId = ":dashboardId", + ModelID = ":id", +} + +export default RouteParams; diff --git a/App/FeatureSet/PublicDashboard/tsconfig.json b/App/FeatureSet/PublicDashboard/tsconfig.json new file mode 100644 index 0000000000..5b9cd26a92 --- /dev/null +++ b/App/FeatureSet/PublicDashboard/tsconfig.json @@ -0,0 +1,43 @@ +{ + "ts-node": { + "compilerOptions": { + "module": "commonjs", + "resolveJsonModule": true + } + }, + "compilerOptions": { + "target": "es2017", + "jsx": "react", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "module": "es2022", + "moduleResolution": "node", + "typeRoots": [ + "./node_modules/@types" + ], + "types": ["node"], + "sourceMap": true, + "outDir": "./build/dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "useUnknownInCatchVariables": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/App/FeatureSet/PublicDashboard/views/index.ejs b/App/FeatureSet/PublicDashboard/views/index.ejs new file mode 100644 index 0000000000..216eab975b --- /dev/null +++ b/App/FeatureSet/PublicDashboard/views/index.ejs @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + <%= typeof title !== 'undefined' ? title : 'Dashboard' %> + + <% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %> + + + + <% } %> + + + + <% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %> + + + + <% } %> + +
+ + + + diff --git a/App/FeatureSet/StatusPage/src/Components/Monitor/MonitorOverview.tsx b/App/FeatureSet/StatusPage/src/Components/Monitor/MonitorOverview.tsx index da94e0664e..58f3fb9ff7 100644 --- a/App/FeatureSet/StatusPage/src/Components/Monitor/MonitorOverview.tsx +++ b/App/FeatureSet/StatusPage/src/Components/Monitor/MonitorOverview.tsx @@ -30,6 +30,7 @@ export interface ComponentProps { statusPageHistoryChartBarColorRules: Array; downtimeMonitorStatuses: Array; defaultBarColor: Color; + uptimeHistoryDays?: number | undefined; } const MonitorOverview: FunctionComponent = ( @@ -143,7 +144,7 @@ const MonitorOverview: FunctionComponent = ( {/* Time labels: Visible on all screen sizes */} {props.showHistoryChart && (
-
90 days ago
+
{props.uptimeHistoryDays || 90} days ago
Today
)} diff --git a/App/FeatureSet/StatusPage/src/Pages/Overview/Overview.tsx b/App/FeatureSet/StatusPage/src/Pages/Overview/Overview.tsx index 897aac62a7..a468e1f6e4 100644 --- a/App/FeatureSet/StatusPage/src/Pages/Overview/Overview.tsx +++ b/App/FeatureSet/StatusPage/src/Pages/Overview/Overview.tsx @@ -126,7 +126,9 @@ const Overview: FunctionComponent = ( scheduledMaintenanceStateTimelines, setScheduledMaintenanceStateTimelines, ] = useState>([]); - const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); + const uptimeHistoryDays: number = + statusPage?.showUptimeHistoryInDays || 90; + const startDate: Date = OneUptimeDate.getSomeDaysAgo(uptimeHistoryDays); const endDate: Date = OneUptimeDate.getCurrentDate(); const [currentStatus, setCurrentStatus] = useState( null, @@ -493,6 +495,7 @@ const Overview: FunctionComponent = ( showCurrentStatus={resource.showCurrentStatus} uptimeGraphHeight={10} defaultBarColor={statusPage?.defaultBarColor || Green} + uptimeHistoryDays={uptimeHistoryDays} />, ); } @@ -548,6 +551,7 @@ const Overview: FunctionComponent = ( showCurrentStatus={resource.showCurrentStatus} uptimeGraphHeight={10} defaultBarColor={statusPage?.defaultBarColor || Green} + uptimeHistoryDays={uptimeHistoryDays} />, ); } diff --git a/App/package.json b/App/package.json index da2aaf7a56..a84e0a36ee 100644 --- a/App/package.json +++ b/App/package.json @@ -12,16 +12,19 @@ "build-frontend:dashboard": "bash ./scripts/frontend-run.sh FeatureSet/Dashboard dev-build", "build-frontend:admin-dashboard": "bash ./scripts/frontend-run.sh FeatureSet/AdminDashboard dev-build", "build-frontend:status-page": "bash ./scripts/frontend-run.sh FeatureSet/StatusPage dev-build", - "build-frontends": "npm run build-frontend:accounts && npm run build-frontend:dashboard && npm run build-frontend:admin-dashboard && npm run build-frontend:status-page", + "build-frontend:public-dashboard": "bash ./scripts/frontend-run.sh FeatureSet/PublicDashboard dev-build", + "build-frontends": "npm run build-frontend:accounts && npm run build-frontend:dashboard && npm run build-frontend:admin-dashboard && npm run build-frontend:status-page && npm run build-frontend:public-dashboard", "build-frontend:accounts:prod": "bash ./scripts/frontend-run.sh FeatureSet/Accounts build", "build-frontend:dashboard:prod": "bash ./scripts/frontend-run.sh FeatureSet/Dashboard build", "build-frontend:admin-dashboard:prod": "bash ./scripts/frontend-run.sh FeatureSet/AdminDashboard build", "build-frontend:status-page:prod": "bash ./scripts/frontend-run.sh FeatureSet/StatusPage build", - "build-frontends:prod": "npm run build-frontend:accounts:prod && npm run build-frontend:dashboard:prod && npm run build-frontend:admin-dashboard:prod && npm run build-frontend:status-page:prod", + "build-frontend:public-dashboard:prod": "bash ./scripts/frontend-run.sh FeatureSet/PublicDashboard build", + "build-frontends:prod": "npm run build-frontend:accounts:prod && npm run build-frontend:dashboard:prod && npm run build-frontend:admin-dashboard:prod && npm run build-frontend:status-page:prod && npm run build-frontend:public-dashboard:prod", "watch-frontend:accounts": "bash ./scripts/frontend-run.sh FeatureSet/Accounts dev-build --watch", "watch-frontend:dashboard": "bash ./scripts/frontend-run.sh FeatureSet/Dashboard dev-build --watch", "watch-frontend:admin-dashboard": "bash ./scripts/frontend-run.sh FeatureSet/AdminDashboard dev-build --watch", "watch-frontend:status-page": "bash ./scripts/frontend-run.sh FeatureSet/StatusPage dev-build --watch", + "watch-frontend:public-dashboard": "bash ./scripts/frontend-run.sh FeatureSet/PublicDashboard dev-build --watch", "dev:api": "npx nodemon", "start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts", "compile": "tsc", diff --git a/App/scripts/dev.sh b/App/scripts/dev.sh index eceb71ac61..780b3b424c 100644 --- a/App/scripts/dev.sh +++ b/App/scripts/dev.sh @@ -28,6 +28,9 @@ pids+=($!) npm run watch-frontend:status-page & pids+=($!) +npm run watch-frontend:public-dashboard & +pids+=($!) + npm run dev:api & pids+=($!) diff --git a/Common/Models/DatabaseModels/Dashboard.ts b/Common/Models/DatabaseModels/Dashboard.ts index f12723914a..84230e3378 100644 --- a/Common/Models/DatabaseModels/Dashboard.ts +++ b/Common/Models/DatabaseModels/Dashboard.ts @@ -1,7 +1,9 @@ import Project from "./Project"; import User from "./User"; import Route from "../../Types/API/Route"; +import { PlanType } from "../../Types/Billing/SubscriptionPlan"; import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl"; +import ColumnBillingAccessControl from "../../Types/Database/AccessControl/ColumnBillingAccessControl"; import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl"; import ColumnLength from "../../Types/Database/ColumnLength"; import ColumnType from "../../Types/Database/ColumnType"; @@ -15,6 +17,7 @@ import TableMetadata from "../../Types/Database/TableMetadata"; import TenantColumn from "../../Types/Database/TenantColumn"; import UniqueColumnBy from "../../Types/Database/UniqueColumnBy"; import IconProp from "../../Types/Icon/IconProp"; +import HashedString from "../../Types/HashedString"; import ObjectID from "../../Types/ObjectID"; import Permission from "../../Types/Permission"; import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel"; @@ -448,4 +451,147 @@ export default class Dashboard extends BaseModel { type: ColumnType.JSON, }) public dashboardViewConfig?: DashboardViewConfig = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateDashboard, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboard, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditDashboard, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Public Dashboard", + description: "Is this dashboard public?", + defaultValue: false, + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + @ColumnBillingAccessControl({ + read: PlanType.Free, + update: PlanType.Growth, + create: PlanType.Free, + }) + public isPublicDashboard?: boolean = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateDashboard, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboard, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditDashboard, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Enable Master Password", + description: + "Require visitors to enter a master password before viewing a private dashboard.", + defaultValue: false, + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public enableMasterPassword?: boolean = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateDashboard, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboard, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditDashboard, + ], + }) + @TableColumn({ + title: "Master Password", + description: + "Password required to unlock a private dashboard. This value is stored as a secure hash.", + hashed: true, + type: TableColumnType.HashedString, + placeholder: "Enter a new master password", + }) + @Column({ + type: ColumnType.HashedString, + length: ColumnLength.HashedString, + nullable: true, + transformer: HashedString.getDatabaseTransformer(), + }) + public masterPassword?: HashedString = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateDashboard, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboard, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditDashboard, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.VeryLongText, + title: "IP Whitelist", + description: + "IP Whitelist for this Dashboard. One IP per line. Only used if the dashboard is private.", + }) + @Column({ + type: ColumnType.VeryLongText, + nullable: true, + }) + @ColumnBillingAccessControl({ + read: PlanType.Free, + update: PlanType.Scale, + create: PlanType.Free, + }) + public ipWhitelist?: string = undefined; } diff --git a/Common/Models/DatabaseModels/DashboardDomain.ts b/Common/Models/DatabaseModels/DashboardDomain.ts new file mode 100644 index 0000000000..aadbb0c0c4 --- /dev/null +++ b/Common/Models/DatabaseModels/DashboardDomain.ts @@ -0,0 +1,659 @@ +import Dashboard from "./Dashboard"; +import Domain from "./Domain"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel"; +import Route from "../../Types/API/Route"; +import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "../../Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "../../Types/Database/ColumnLength"; +import ColumnType from "../../Types/Database/ColumnType"; +import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "../../Types/Database/EnableDocumentation"; +import EnableWorkflow from "../../Types/Database/EnableWorkflow"; +import TableColumn from "../../Types/Database/TableColumn"; +import TableColumnType from "../../Types/Database/TableColumnType"; +import TableMetadata from "../../Types/Database/TableMetadata"; +import TenantColumn from "../../Types/Database/TenantColumn"; +import UniqueColumnBy from "../../Types/Database/UniqueColumnBy"; +import IconProp from "../../Types/Icon/IconProp"; +import ObjectID from "../../Types/ObjectID"; +import Permission from "../../Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; + +@EnableDocumentation() +@CanAccessIfCanReadOn("dashboard") +@TenantColumn("projectId") +@TableAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteDashboardDomain, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditDashboardDomain, + ], +}) +@EnableWorkflow({ + create: true, + delete: true, + update: true, + read: true, +}) +@CrudApiEndpoint(new Route("/dashboard-domain")) +@TableMetadata({ + tableName: "DashboardDomain", + singularName: "Dashboard Domain", + pluralName: "Dashboard Domains", + icon: IconProp.Globe, + tableDescription: "Manage custom domains for your dashboard", +}) +@Entity({ + name: "DashboardDomain", +}) +export default class DashboardDomain extends BaseModel { + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "projectId", + type: TableColumnType.Entity, + modelType: Project, + title: "Project", + description: "Relation to Project Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Project; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Project ID", + description: "ID of your OneUptime Project in which this object belongs", + example: "5f8b9c0d-e1a2-4b3c-8d5e-6f7a8b9c0d1e", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public projectId?: ObjectID = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "domainId", + type: TableColumnType.Entity, + modelType: Domain, + }) + @ManyToOne( + () => { + return Domain; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "domainId" }) + public domain?: Domain = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + example: "d4e5f6a7-b8c9-0123-def4-567890abcdef", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public domainId?: ObjectID = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "dashboardId", + type: TableColumnType.Entity, + modelType: Dashboard, + title: "Dashboard", + description: + "Relation to Dashboard Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Dashboard; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "dashboardId" }) + public dashboard?: Dashboard = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Dashboard ID", + description: "ID of your Dashboard resource where this object belongs", + example: "b2c3d4e5-f6a7-8901-bcde-f1234567890a", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public dashboardId?: ObjectID = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditDashboardDomain, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + title: "Subdomain", + description: + "Subdomain label for your dashboard such as 'dashboard'. Leave blank or enter @ to use the root domain.", + example: "dashboard", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public subdomain?: string = undefined; + + @UniqueColumnBy("projectId") + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + required: false, + computed: true, + type: TableColumnType.ShortText, + title: "Full Domain", + description: + "Full domain of your dashboard (like dashboard.acmeinc.com). This is autogenerated and is derived from subdomain and domain.", + example: "dashboard.acmeinc.com", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public fullDomain?: string = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "createdByUserId", + type: TableColumnType.Entity, + modelType: User, + title: "Created by User", + description: + "Relation to User who created this object (if this object was created by a User)", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "SET NULL", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Created by User ID", + description: + "User ID who created this object (if this object was created by a User)", + example: "c3d4e5f6-a7b8-9012-cdef-234567890abc", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public createdByUserId?: ObjectID = undefined; + + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by User", + modelType: User, + description: + "Relation to User who deleted this object (if this object was deleted by a User)", + }) + @ManyToOne( + () => { + return User; + }, + { + cascade: false, + eager: false, + nullable: true, + onDelete: "SET NULL", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: false, + computed: true, + type: TableColumnType.ShortText, + title: "CNAME Verification Token", + description: "CNAME Verification Token", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public cnameVerificationToken?: string = undefined; + + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + title: "CNAME Verified", + description: "Is CNAME Verified?", + defaultValue: false, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isCnameVerified?: boolean = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + computed: true, + type: TableColumnType.Boolean, + title: "SSL Ordered", + description: "Is SSL ordered?", + defaultValue: false, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isSslOrdered?: boolean = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + computed: true, + type: TableColumnType.Boolean, + title: "SSL Provisioned", + description: "Is SSL provisioned?", + defaultValue: false, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isSslProvisioned?: boolean = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Deleted by User ID", + description: + "User ID who deleted this object (if this object was deleted by a User)", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public deletedByUserId?: ObjectID = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditDashboardDomain, + ], + }) + @TableColumn({ type: TableColumnType.VeryLongText }) + @Column({ + type: ColumnType.VeryLongText, + nullable: true, + unique: false, + }) + public customCertificate?: string = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditDashboardDomain, + ], + }) + @TableColumn({ type: TableColumnType.VeryLongText }) + @Column({ + type: ColumnType.VeryLongText, + nullable: true, + unique: false, + }) + public customCertificateKey?: string = undefined; + + // If this is true, then the certificate is custom and not managed by OneUptime (LetsEncrypt) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateDashboardDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadDashboardDomain, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditDashboardDomain, + ], + }) + @TableColumn({ type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isCustomCertificate?: boolean = undefined; +} diff --git a/Common/Models/DatabaseModels/Index.ts b/Common/Models/DatabaseModels/Index.ts index 1a387fe9b4..10e9b7214e 100644 --- a/Common/Models/DatabaseModels/Index.ts +++ b/Common/Models/DatabaseModels/Index.ts @@ -224,6 +224,7 @@ import IncidentSla from "./IncidentSla"; import TableView from "./TableView"; import Dashboard from "./Dashboard"; +import DashboardDomain from "./DashboardDomain"; import MonitorTest from "./MonitorTest"; import ScheduledMaintenanceFeed from "./ScheduledMaintenanceFeed"; @@ -486,6 +487,7 @@ const AllModelTypes: Array<{ // Dashboards Dashboard, + DashboardDomain, MonitorTest, diff --git a/Common/Models/DatabaseModels/StatusPage.ts b/Common/Models/DatabaseModels/StatusPage.ts index b0930ea3fb..ab75498fc3 100755 --- a/Common/Models/DatabaseModels/StatusPage.ts +++ b/Common/Models/DatabaseModels/StatusPage.ts @@ -2638,6 +2638,47 @@ export default class StatusPage extends BaseModel { }) public enableEmbeddedOverallStatus?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + Permission.ReadAllProjectResources, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Number, + title: "Show Uptime History In Days", + description: + "How many days of uptime history should be shown on the status page? Maximum is 90 days.", + defaultValue: 90, + }) + @Column({ + type: ColumnType.Number, + default: 90, + nullable: false, + }) + @ColumnBillingAccessControl({ + read: PlanType.Free, + update: PlanType.Free, + create: PlanType.Free, + }) + public showUptimeHistoryInDays?: number = undefined; + @ColumnAccessControl({ create: [ Permission.ProjectOwner, diff --git a/Common/Server/API/DashboardAPI.ts b/Common/Server/API/DashboardAPI.ts new file mode 100644 index 0000000000..95f522a48b --- /dev/null +++ b/Common/Server/API/DashboardAPI.ts @@ -0,0 +1,312 @@ +import UserMiddleware from "../Middleware/UserAuthorization"; +import DashboardService, { + Service as DashboardServiceType, +} from "../Services/DashboardService"; +import DashboardDomainService from "../Services/DashboardDomainService"; +import CookieUtil from "../Utils/Cookie"; +import logger from "../Utils/Logger"; +import { + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import BadDataException from "../../Types/Exception/BadDataException"; +import NotFoundException from "../../Types/Exception/NotFoundException"; +import HashedString from "../../Types/HashedString"; +import ObjectID from "../../Types/ObjectID"; +import Dashboard from "../../Models/DatabaseModels/Dashboard"; +import DashboardDomain from "../../Models/DatabaseModels/DashboardDomain"; +import { EncryptionSecret } from "../EnvironmentConfig"; +import { DASHBOARD_MASTER_PASSWORD_INVALID_MESSAGE } from "../../Types/Dashboard/MasterPassword"; + +export default class DashboardAPI extends BaseAPI< + Dashboard, + DashboardServiceType +> { + public constructor() { + super(Dashboard, DashboardService); + + // SEO endpoint - resolve dashboard by ID or domain + this.router.get( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/seo/:dashboardIdOrDomain`, + UserMiddleware.getUserMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ) => { + try { + const dashboardIdOrDomain: string = req.params[ + "dashboardIdOrDomain" + ] as string; + + let dashboardId: ObjectID | null = null; + + if ( + dashboardIdOrDomain && + dashboardIdOrDomain.includes(".") + ) { + // This is a domain - resolve to dashboard ID + const dashboardDomain: DashboardDomain | null = + await DashboardDomainService.findOneBy({ + query: { + fullDomain: dashboardIdOrDomain, + domain: { + isVerified: true, + } as any, + }, + select: { + dashboardId: true, + }, + props: { + isRoot: true, + }, + }); + + if (!dashboardDomain || !dashboardDomain.dashboardId) { + return Response.sendErrorResponse( + req, + res, + new NotFoundException("Dashboard not found"), + ); + } + + dashboardId = dashboardDomain.dashboardId; + } else { + try { + dashboardId = new ObjectID(dashboardIdOrDomain); + } catch (err) { + logger.error(err); + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid dashboard ID"), + ); + } + } + + const dashboard: Dashboard | null = + await DashboardService.findOneById({ + id: dashboardId, + select: { + _id: true, + name: true, + description: true, + }, + props: { + isRoot: true, + }, + }); + + if (!dashboard) { + return Response.sendErrorResponse( + req, + res, + new NotFoundException("Dashboard not found"), + ); + } + + return Response.sendJsonObjectResponse(req, res, { + _id: dashboard._id?.toString() || "", + title: dashboard.name || "Dashboard", + description: + dashboard.description || + "View dashboard metrics and insights.", + }); + } catch (err) { + next(err); + } + }, + ); + + // Domain resolution endpoint + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/domain`, + UserMiddleware.getUserMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ) => { + try { + if (!req.body["domain"]) { + throw new BadDataException( + "domain is required in request body", + ); + } + + const domain: string = req.body["domain"] as string; + + const dashboardDomain: DashboardDomain | null = + await DashboardDomainService.findOneBy({ + query: { + fullDomain: domain, + domain: { + isVerified: true, + } as any, + }, + select: { + dashboardId: true, + }, + props: { + isRoot: true, + }, + }); + + if (!dashboardDomain) { + throw new BadDataException( + "No dashboard found with this domain", + ); + } + + const objectId: ObjectID = dashboardDomain.dashboardId!; + + return Response.sendJsonObjectResponse(req, res, { + dashboardId: objectId.toString(), + }); + } catch (err) { + next(err); + } + }, + ); + + // Metadata endpoint - returns dashboard info for the public viewer + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/metadata/:dashboardId`, + UserMiddleware.getUserMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ) => { + try { + const dashboardId: ObjectID = new ObjectID( + req.params["dashboardId"] as string, + ); + + const dashboard: Dashboard | null = + await DashboardService.findOneById({ + id: dashboardId, + select: { + _id: true, + name: true, + description: true, + isPublicDashboard: true, + enableMasterPassword: true, + }, + props: { + isRoot: true, + }, + }); + + if (!dashboard) { + throw new NotFoundException("Dashboard not found"); + } + + return Response.sendJsonObjectResponse(req, res, { + _id: dashboard._id?.toString() || "", + name: dashboard.name || "Dashboard", + description: + dashboard.description || "", + isPublicDashboard: dashboard.isPublicDashboard || false, + enableMasterPassword: + dashboard.enableMasterPassword || false, + }); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/master-password/:dashboardId`, + UserMiddleware.getUserMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ) => { + try { + if (!req.params["dashboardId"]) { + throw new BadDataException("Dashboard ID not found"); + } + + const dashboardId: ObjectID = new ObjectID( + req.params["dashboardId"] as string, + ); + + const password: string | undefined = + req.body && (req.body["password"] as string); + + if (!password) { + throw new BadDataException("Master password is required."); + } + + const dashboard: Dashboard | null = + await DashboardService.findOneById({ + id: dashboardId, + select: { + _id: true, + projectId: true, + enableMasterPassword: true, + masterPassword: true, + isPublicDashboard: true, + }, + props: { + isRoot: true, + }, + }); + + if (!dashboard) { + throw new NotFoundException("Dashboard not found"); + } + + if (dashboard.isPublicDashboard) { + throw new BadDataException( + "This dashboard is already visible to everyone.", + ); + } + + if ( + !dashboard.enableMasterPassword || + !dashboard.masterPassword + ) { + throw new BadDataException( + "Master password has not been configured for this dashboard.", + ); + } + + const hashedInput: string = await HashedString.hashValue( + password, + EncryptionSecret, + ); + + if (hashedInput !== dashboard.masterPassword.toString()) { + throw new BadDataException( + DASHBOARD_MASTER_PASSWORD_INVALID_MESSAGE, + ); + } + + CookieUtil.setDashboardMasterPasswordCookie({ + expressResponse: res, + dashboardId, + }); + + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + next(err); + } + }, + ); + } +} diff --git a/Common/Server/API/DashboardDomainAPI.ts b/Common/Server/API/DashboardDomainAPI.ts new file mode 100644 index 0000000000..48cc4f867b --- /dev/null +++ b/Common/Server/API/DashboardDomainAPI.ts @@ -0,0 +1,244 @@ +import { DashboardCNameRecord } from "../EnvironmentConfig"; +import UserMiddleware from "../Middleware/UserAuthorization"; +import DashboardDomainService, { + Service as DashboardDomainServiceType, +} from "../Services/DashboardDomainService"; +import { + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import logger from "../Utils/Logger"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import CommonAPI from "./CommonAPI"; +import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps"; +import BadDataException from "../../Types/Exception/BadDataException"; +import ObjectID from "../../Types/ObjectID"; +import PositiveNumber from "../../Types/PositiveNumber"; +import DashboardDomain from "../../Models/DatabaseModels/DashboardDomain"; + +export default class DashboardDomainAPI extends BaseAPI< + DashboardDomain, + DashboardDomainServiceType +> { + public constructor() { + super(DashboardDomain, DashboardDomainService); + + // CNAME verification api + this.router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/verify-cname/:id`, + UserMiddleware.getUserMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ) => { + try { + if (!DashboardCNameRecord) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + `Custom Domains not enabled for this + OneUptime installation. Please contact + your server admin to enable this + feature.`, + ), + ); + } + + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); + + const id: ObjectID = new ObjectID(req.params["id"] as string); + + const domainCount: PositiveNumber = + await DashboardDomainService.countBy({ + query: { + _id: id.toString(), + }, + props: databaseProps, + }); + + if (domainCount.toNumber() === 0) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "The domain does not exist or user does not have access to it.", + ), + ); + } + + const domain: DashboardDomain | null = + await DashboardDomainService.findOneBy({ + query: { + _id: id.toString(), + }, + select: { + _id: true, + fullDomain: true, + }, + props: { + isRoot: true, + }, + }); + + if (!domain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token."), + ); + } + + if (!domain.fullDomain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid domain."), + ); + } + + const isValid: boolean = + await DashboardDomainService.isCnameValid(domain.fullDomain!); + + if (!isValid) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "CNAME is not verified. Please make sure you have the correct record and please verify CNAME again. If you are sure that the record is correct, please wait for some time for the DNS to propagate.", + ), + ); + } + + return Response.sendEmptySuccessResponse(req, res); + } catch (e) { + next(e); + } + }, + ); + + // Provision SSL API + this.router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/order-ssl/:id`, + UserMiddleware.getUserMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ) => { + try { + if (!DashboardCNameRecord) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + `Custom Domains not enabled for this + OneUptime installation. Please contact + your server admin to enable this + feature.`, + ), + ); + } + + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); + + const id: ObjectID = new ObjectID(req.params["id"] as string); + + const domainCount: PositiveNumber = + await DashboardDomainService.countBy({ + query: { + _id: id.toString(), + }, + props: databaseProps, + }); + + if (domainCount.toNumber() === 0) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "The domain does not exist or user does not have access to it.", + ), + ); + } + + const domain: DashboardDomain | null = + await DashboardDomainService.findOneBy({ + query: { + _id: id.toString(), + }, + select: { + _id: true, + fullDomain: true, + cnameVerificationToken: true, + isCnameVerified: true, + isSslProvisioned: true, + }, + props: { + isRoot: true, + }, + }); + + if (!domain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token."), + ); + } + + if (!domain.cnameVerificationToken) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token."), + ); + } + + if (!domain.isCnameVerified) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "CNAME is not verified. Please verify CNAME first before you provision SSL.", + ), + ); + } + + if (domain.isSslProvisioned) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("SSL is already provisioned."), + ); + } + + if (!domain.fullDomain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid domain."), + ); + } + + logger.debug("Ordering SSL"); + + await DashboardDomainService.orderCert(domain); + + logger.debug( + "SSL Provisioned for domain - " + domain.fullDomain, + ); + + return Response.sendEmptySuccessResponse(req, res); + } catch (e) { + next(e); + } + }, + ); + } +} diff --git a/Common/Server/API/StatusPageAPI.ts b/Common/Server/API/StatusPageAPI.ts index 7a3d7a86b7..6a77548a43 100644 --- a/Common/Server/API/StatusPageAPI.ts +++ b/Common/Server/API/StatusPageAPI.ts @@ -1443,7 +1443,33 @@ export default class StatusPageAPI extends BaseAPI< req: req, }); - const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); + // First fetch the status page to get the configured uptime history days + const statusPageForDays: StatusPage | null = + await StatusPageService.findOneBy({ + query: { + _id: statusPageId.toString(), + }, + select: { + showUptimeHistoryInDays: true, + }, + props: { + isRoot: true, + }, + }); + + let uptimeHistoryDays: number = + statusPageForDays?.showUptimeHistoryInDays || 90; + + if (uptimeHistoryDays > 90) { + uptimeHistoryDays = 90; + } + + if (uptimeHistoryDays < 1) { + uptimeHistoryDays = 1; + } + + const startDate: Date = + OneUptimeDate.getSomeDaysAgo(uptimeHistoryDays); const endDate: Date = OneUptimeDate.getCurrentDate(); const { @@ -4448,6 +4474,7 @@ export default class StatusPageAPI extends BaseAPI< showIncidentsOnStatusPage: true, showEpisodesOnStatusPage: true, showScheduledMaintenanceEventsOnStatusPage: true, + showUptimeHistoryInDays: true, }, props: { isRoot: true, diff --git a/Common/Server/EnvironmentConfig.ts b/Common/Server/EnvironmentConfig.ts index 76c4947eb2..c1a6a3808b 100644 --- a/Common/Server/EnvironmentConfig.ts +++ b/Common/Server/EnvironmentConfig.ts @@ -35,6 +35,7 @@ const FRONTEND_ENV_ALLOW_LIST: Array = [ "VAPID_SUBJECT", "VERSION", "STATUS_PAGE_CNAME_RECORD", + "DASHBOARD_CNAME_RECORD", "ANALYTICS_KEY", "ANALYTICS_HOST", "GIT_SHA", @@ -252,6 +253,9 @@ export const ClickhouseHost: Hostname = Hostname.fromString( export const StatusPageCNameRecord: string = process.env["STATUS_PAGE_CNAME_RECORD"] || ""; +export const DashboardCNameRecord: string = + process.env["DASHBOARD_CNAME_RECORD"] || ""; + export const ClickhousePort: Port = new Port( process.env["CLICKHOUSE_PORT"] || "8123", ); diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1774524742177-MigrationName.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1774524742177-MigrationName.ts new file mode 100644 index 0000000000..4a03f534f6 --- /dev/null +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1774524742177-MigrationName.ts @@ -0,0 +1,42 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class MigrationName1774524742177 implements MigrationInterface { + name = 'MigrationName1774524742177' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "DashboardDomain" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "domainId" uuid NOT NULL, "dashboardId" uuid NOT NULL, "subdomain" character varying(100) NOT NULL, "fullDomain" character varying(100) NOT NULL, "createdByUserId" uuid, "cnameVerificationToken" character varying(100) NOT NULL, "isCnameVerified" boolean NOT NULL DEFAULT false, "isSslOrdered" boolean NOT NULL DEFAULT false, "isSslProvisioned" boolean NOT NULL DEFAULT false, "deletedByUserId" uuid, "customCertificate" text, "customCertificateKey" text, "isCustomCertificate" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_3897ff3212d5d8ddbdeca684bf6" PRIMARY KEY ("_id"))`); + await queryRunner.query(`CREATE INDEX "IDX_8c0e357d0490d45c89ee673005" ON "DashboardDomain" ("projectId") `); + await queryRunner.query(`CREATE INDEX "IDX_0f58973f28172817bf9c1b34e7" ON "DashboardDomain" ("domainId") `); + await queryRunner.query(`CREATE INDEX "IDX_601f68ad16b421ede8b06b3f40" ON "DashboardDomain" ("dashboardId") `); + await queryRunner.query(`ALTER TABLE "Dashboard" ADD "isPublicDashboard" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "Dashboard" ADD "enableMasterPassword" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "Dashboard" ADD "masterPassword" character varying(64)`); + await queryRunner.query(`ALTER TABLE "Dashboard" ADD "ipWhitelist" text`); + await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`); + await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" ADD CONSTRAINT "FK_8c0e357d0490d45c89ee673005c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" ADD CONSTRAINT "FK_0f58973f28172817bf9c1b34e73" FOREIGN KEY ("domainId") REFERENCES "Domain"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" ADD CONSTRAINT "FK_601f68ad16b421ede8b06b3f40c" FOREIGN KEY ("dashboardId") REFERENCES "Dashboard"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" ADD CONSTRAINT "FK_de80950ba9f0d034f5c47940b3c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" ADD CONSTRAINT "FK_de0c87b9c94b5dfeb21f1ce106f" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "DashboardDomain" DROP CONSTRAINT "FK_de0c87b9c94b5dfeb21f1ce106f"`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" DROP CONSTRAINT "FK_de80950ba9f0d034f5c47940b3c"`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" DROP CONSTRAINT "FK_601f68ad16b421ede8b06b3f40c"`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" DROP CONSTRAINT "FK_0f58973f28172817bf9c1b34e73"`); + await queryRunner.query(`ALTER TABLE "DashboardDomain" DROP CONSTRAINT "FK_8c0e357d0490d45c89ee673005c"`); + await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`); + await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`); + await queryRunner.query(`ALTER TABLE "Dashboard" DROP COLUMN "ipWhitelist"`); + await queryRunner.query(`ALTER TABLE "Dashboard" DROP COLUMN "masterPassword"`); + await queryRunner.query(`ALTER TABLE "Dashboard" DROP COLUMN "enableMasterPassword"`); + await queryRunner.query(`ALTER TABLE "Dashboard" DROP COLUMN "isPublicDashboard"`); + await queryRunner.query(`DROP INDEX "public"."IDX_601f68ad16b421ede8b06b3f40"`); + await queryRunner.query(`DROP INDEX "public"."IDX_0f58973f28172817bf9c1b34e7"`); + await queryRunner.query(`DROP INDEX "public"."IDX_8c0e357d0490d45c89ee673005"`); + await queryRunner.query(`DROP TABLE "DashboardDomain"`); + } + +} diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1774524742178-MigrationName.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1774524742178-MigrationName.ts new file mode 100644 index 0000000000..fb1cf2a9ea --- /dev/null +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1774524742178-MigrationName.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class MigrationName1774524742178 implements MigrationInterface { + name = 'MigrationName1774524742178' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "StatusPage" ADD "showUptimeHistoryInDays" integer NOT NULL DEFAULT 90`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "StatusPage" DROP COLUMN "showUptimeHistoryInDays"`); + } + +} diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts index 38f37cdf54..b169716371 100644 --- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts @@ -270,6 +270,8 @@ import { MigrationName1774000000000 } from "./1774000000000-MigrationName"; import { MigrationName1774000000001 } from "./1774000000001-MigrationName"; import { MigrationName1774355321449 } from "./1774355321449-MigrationName"; import { MigrationName1774357353502 } from "./1774357353502-MigrationName"; +import { MigrationName1774524742177 } from "./1774524742177-MigrationName"; +import { MigrationName1774524742178 } from "./1774524742178-MigrationName"; export default [ InitialMigration, @@ -544,4 +546,6 @@ export default [ MigrationName1774000000001, MigrationName1774355321449, MigrationName1774357353502, + MigrationName1774524742177, + MigrationName1774524742178 ]; diff --git a/Common/Server/Services/DashboardDomainService.ts b/Common/Server/Services/DashboardDomainService.ts new file mode 100644 index 0000000000..65db0089c0 --- /dev/null +++ b/Common/Server/Services/DashboardDomainService.ts @@ -0,0 +1,655 @@ +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import GreenlockUtil from "../Utils/Greenlock/Greenlock"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import DomainService from "./DomainService"; +import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse"; +import HTTPResponse from "../../Types/API/HTTPResponse"; +import URL from "../../Types/API/URL"; +import LIMIT_MAX from "../../Types/Database/LimitMax"; +import BadDataException from "../../Types/Exception/BadDataException"; +import { JSONObject } from "../../Types/JSON"; +import ObjectID from "../../Types/ObjectID"; +import API from "../../Utils/API"; +import AcmeCertificate from "../../Models/DatabaseModels/AcmeCertificate"; +import DomainModel from "../../Models/DatabaseModels/Domain"; +import DashboardDomain from "../../Models/DatabaseModels/DashboardDomain"; +import AcmeCertificateService from "./AcmeCertificateService"; +import Telemetry, { Span } from "../Utils/Telemetry"; +import CaptureSpan from "../Utils/Telemetry/CaptureSpan"; +import { DashboardCNameRecord } from "../EnvironmentConfig"; +import Domain from "../Types/Domain"; + +export class Service extends DatabaseService { + public constructor() { + super(DashboardDomain); + } + + @CaptureSpan() + protected override async onBeforeCreate( + createBy: CreateBy, + ): Promise> { + const domain: DomainModel | null = await DomainService.findOneBy({ + query: { + _id: + createBy.data.domainId?.toString() || + createBy.data.domain?._id || + "", + }, + select: { domain: true, isVerified: true }, + props: { + isRoot: true, + }, + }); + + if (!domain?.isVerified) { + throw new BadDataException( + "This domain is not verified. Please verify it by going to Settings > Domains", + ); + } + + let normalizedSubdomain: string = + createBy.data.subdomain?.trim().toLowerCase() || ""; + + if (normalizedSubdomain === "@") { + normalizedSubdomain = ""; + } + + createBy.data.subdomain = normalizedSubdomain; + + if (domain) { + const baseDomain: string = + domain.domain?.toString().toLowerCase().trim() || ""; + + if (!baseDomain) { + throw new BadDataException("Please select a valid domain."); + } + + createBy.data.fullDomain = normalizedSubdomain + ? `${normalizedSubdomain}.${baseDomain}` + : baseDomain; + } + + createBy.data.cnameVerificationToken = ObjectID.generate().toString(); + + if (createBy.data.isCustomCertificate) { + if ( + !createBy.data.customCertificate || + !createBy.data.customCertificateKey + ) { + throw new BadDataException( + "Custom certificate or private key is missing", + ); + } + } + + return { createBy, carryForward: null }; + } + + @CaptureSpan() + protected override async onBeforeDelete( + deleteBy: DeleteBy, + ): Promise> { + const domains: Array = await this.findBy({ + query: { + ...deleteBy.query, + }, + skip: 0, + limit: LIMIT_MAX, + select: { fullDomain: true }, + props: { + isRoot: true, + }, + }); + + return { deleteBy, carryForward: domains }; + } + + @CaptureSpan() + protected override async onDeleteSuccess( + onDelete: OnDelete, + _itemIdsBeforeDelete: ObjectID[], + ): Promise> { + for (const domain of onDelete.carryForward) { + await this.removeDomainFromGreenlock(domain.fullDomain as string); + } + + return onDelete; + } + + @CaptureSpan() + public async removeDomainFromGreenlock(domain: string): Promise { + await GreenlockUtil.removeDomain(domain); + } + + @CaptureSpan() + public async orderCert(dashboardDomain: DashboardDomain): Promise { + return Telemetry.startActiveSpan>({ + name: "DashboardDomainService.orderCert", + options: { + attributes: { + fullDomain: dashboardDomain.fullDomain, + _id: dashboardDomain.id?.toString(), + }, + }, + fn: async (span: Span): Promise => { + try { + if (!dashboardDomain.fullDomain) { + const fetchedDashboardDomain: DashboardDomain | null = + await this.findOneBy({ + query: { + _id: dashboardDomain.id!.toString(), + }, + select: { + _id: true, + fullDomain: true, + }, + props: { + isRoot: true, + }, + }); + + if (!fetchedDashboardDomain) { + throw new BadDataException("DomainModel not found"); + } + + dashboardDomain = fetchedDashboardDomain; + } + + if (!dashboardDomain.fullDomain) { + throw new BadDataException( + "Unable to order certificate because domain is null", + ); + } + + logger.debug( + "Ordering SSL for domain: " + dashboardDomain.fullDomain, + ); + + await GreenlockUtil.orderCert({ + domain: dashboardDomain.fullDomain as string, + validateCname: async (fullDomain: string) => { + return await this.isCnameValid(fullDomain); + }, + }); + + logger.debug( + "SSL ordered for domain: " + dashboardDomain.fullDomain, + ); + + await this.updateOneById({ + id: dashboardDomain.id!, + data: { + isSslOrdered: true, + }, + props: { + isRoot: true, + }, + }); + + Telemetry.endSpan(span); + } catch (err) { + Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({ + span, + exception: err, + }); + + throw err; + } + }, + }); + } + + @CaptureSpan() + public async updateSslProvisioningStatusForAllDomains(): Promise { + const domains: Array = await this.findBy({ + query: { + isSslOrdered: true, + isCustomCertificate: false, + }, + select: { + _id: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const domain of domains) { + await this.updateSslProvisioningStatus(domain); + } + } + + private async isSSLProvisioned( + fulldomain: string, + token: string, + ): Promise { + try { + const result: HTTPErrorResponse | HTTPResponse = + await API.get({ + url: URL.fromString( + "https://" + + fulldomain + + "/dashboard-api/cname-verification/" + + token, + ), + }); + + if (result.isFailure()) { + return false; + } + + return true; + } catch (err) { + logger.error(err); + return false; + } + } + + @CaptureSpan() + public async updateCnameStatusForDashboardDomain(data: { + domain: string; + cnameStatus: boolean; + }): Promise { + if (!data.cnameStatus) { + await this.updateOneBy({ + query: { + fullDomain: data.domain, + }, + data: { + isCnameVerified: false, + isSslOrdered: false, + isSslProvisioned: false, + }, + props: { + isRoot: true, + }, + }); + } else { + await this.updateOneBy({ + query: { + fullDomain: data.domain, + }, + data: { + isCnameVerified: true, + }, + props: { + isRoot: true, + }, + }); + } + } + + @CaptureSpan() + public async isCnameValid(fullDomain: string): Promise { + try { + logger.debug("Checking for CNAME " + fullDomain); + + const dashboardDomain: DashboardDomain | null = await this.findOneBy({ + query: { + fullDomain: fullDomain, + }, + select: { + _id: true, + cnameVerificationToken: true, + }, + props: { + isRoot: true, + }, + }); + + if (!dashboardDomain) { + return false; + } + + const token: string = dashboardDomain.cnameVerificationToken!; + + logger.debug( + "Checking for CNAME " + fullDomain + " with token " + token, + ); + + try { + const result: HTTPErrorResponse | HTTPResponse = + await API.get({ + url: URL.fromString( + "http://" + + fullDomain + + "/dashboard-api/cname-verification/" + + token, + ), + }); + + logger.debug("CNAME verification result"); + logger.debug(result); + + if (result.isSuccess()) { + await this.updateCnameStatusForDashboardDomain({ + domain: fullDomain, + cnameStatus: true, + }); + + return true; + } + } catch (err) { + logger.debug("Failed checking for CNAME " + fullDomain); + logger.debug(err); + } + + try { + const resultHttps: HTTPErrorResponse | HTTPResponse = + await API.get({ + url: URL.fromString( + "https://" + + fullDomain + + "/dashboard-api/cname-verification/" + + token, + ), + }); + + logger.debug("CNAME verification result for https"); + logger.debug(resultHttps); + + if (resultHttps.isSuccess()) { + await this.updateCnameStatusForDashboardDomain({ + domain: fullDomain, + cnameStatus: true, + }); + + return true; + } + } catch (err) { + logger.debug("Failed checking for CNAME " + fullDomain); + logger.debug(err); + } + + try { + if (DashboardCNameRecord) { + const cnameRecords: Array = await Domain.getCnameRecords({ + domain: fullDomain, + }); + + let cnameRecord: string | undefined = undefined; + if (cnameRecords.length > 0) { + cnameRecord = cnameRecords[0]; + } + + if (!cnameRecord) { + logger.debug( + `No CNAME record found for ${fullDomain}. Expected record: ${DashboardCNameRecord}`, + ); + await this.updateCnameStatusForDashboardDomain({ + domain: fullDomain, + cnameStatus: false, + }); + return false; + } + + if ( + cnameRecord && + cnameRecord.trim().toLocaleLowerCase() === + DashboardCNameRecord.trim().toLocaleLowerCase() + ) { + logger.debug( + `CNAME record for ${fullDomain} matches the expected record: ${DashboardCNameRecord}`, + ); + + await this.updateCnameStatusForDashboardDomain({ + domain: fullDomain, + cnameStatus: true, + }); + + return true; + } + + logger.debug( + `CNAME record for ${fullDomain} is ${cnameRecord} and it does not match the expected record: ${DashboardCNameRecord}`, + ); + } + } catch (err) { + logger.debug("Failed checking for CNAME " + fullDomain); + logger.debug(err); + } + + await this.updateCnameStatusForDashboardDomain({ + domain: fullDomain, + cnameStatus: false, + }); + + return false; + } catch (err) { + logger.debug("Failed checking for CNAME " + fullDomain); + logger.debug(err); + + await this.updateCnameStatusForDashboardDomain({ + domain: fullDomain, + cnameStatus: false, + }); + + return false; + } + } + + @CaptureSpan() + public async updateSslProvisioningStatus( + domain: DashboardDomain, + ): Promise { + if (!domain.id) { + throw new BadDataException("DomainModel ID is required"); + } + + const dashboardDomain: DashboardDomain | null = await this.findOneBy({ + query: { + _id: domain.id?.toString(), + }, + select: { + _id: true, + fullDomain: true, + cnameVerificationToken: true, + }, + props: { + isRoot: true, + }, + }); + + if (!dashboardDomain) { + throw new BadDataException("DomainModel not found"); + } + + logger.debug( + `DashboardCerts:RemoveCerts - Checking CNAME ${dashboardDomain.fullDomain}`, + ); + + const isValid: boolean = await this.isSSLProvisioned( + dashboardDomain.fullDomain!, + dashboardDomain.cnameVerificationToken!, + ); + + if (!isValid) { + const isCnameValid: boolean = await this.isCnameValid( + dashboardDomain.fullDomain!, + ); + + await this.updateOneById({ + id: dashboardDomain.id!, + data: { + isSslProvisioned: false, + }, + props: { + isRoot: true, + }, + }); + + if (isCnameValid) { + try { + await this.orderCert(dashboardDomain); + } catch (err) { + logger.error( + "Cannot order cert for domain: " + dashboardDomain.fullDomain, + ); + logger.error(err); + } + } + } else { + await this.updateOneById({ + id: dashboardDomain.id!, + data: { + isSslProvisioned: true, + }, + props: { + isRoot: true, + }, + }); + } + } + + @CaptureSpan() + public async orderSSLForDomainsWhichAreNotOrderedYet(): Promise { + return Telemetry.startActiveSpan>({ + name: "DashboardDomainService.orderSSLForDomainsWhichAreNotOrderedYet", + options: { attributes: {} }, + fn: async (span: Span): Promise => { + try { + const domains: Array = await this.findBy({ + query: { + isSslOrdered: false, + isCustomCertificate: false, + }, + select: { + _id: true, + fullDomain: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const domain of domains) { + try { + logger.debug("Ordering SSL for domain: " + domain.fullDomain); + await this.orderCert(domain); + } catch (e) { + logger.error(e); + } + } + + Telemetry.endSpan(span); + } catch (err) { + Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({ + span, + exception: err, + }); + + throw err; + } + }, + }); + } + + @CaptureSpan() + public async verifyCnameWhoseCnameisNotVerified(): Promise { + const domains: Array = await this.findBy({ + query: { + isCnameVerified: false, + }, + select: { + _id: true, + fullDomain: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const domain of domains) { + try { + await this.isCnameValid(domain.fullDomain as string); + } catch (e) { + logger.error(e); + } + } + } + + @CaptureSpan() + public async renewCertsWhichAreExpiringSoon(): Promise { + await GreenlockUtil.renewAllCertsWhichAreExpiringSoon({ + validateCname: async (fullDomain: string) => { + return await this.isCnameValid(fullDomain); + }, + notifyDomainRemoved: async (domain: string) => { + await this.updateOneBy({ + query: { + fullDomain: domain, + }, + data: { + isSslOrdered: false, + isSslProvisioned: false, + }, + props: { + isRoot: true, + }, + }); + + logger.debug(`DomainModel removed from greenlock: ${domain}`); + }, + }); + } + + @CaptureSpan() + public async checkOrderStatus(): Promise { + const domains: Array = await this.findBy({ + query: { + isSslOrdered: true, + isCustomCertificate: false, + }, + select: { + _id: true, + fullDomain: true, + cnameVerificationToken: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const domain of domains) { + if (!domain.fullDomain) { + continue; + } + + const acmeCert: AcmeCertificate | null = + await AcmeCertificateService.findOneBy({ + query: { + domain: domain.fullDomain, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (!acmeCert) { + try { + await this.orderCert(domain); + } catch (err) { + logger.error( + "Cannot order cert for domain: " + domain.fullDomain, + ); + logger.error(err); + } + } + } + } +} +export default new Service(); diff --git a/Common/Server/Services/DashboardService.ts b/Common/Server/Services/DashboardService.ts index 8956706dc9..edc28c3ae9 100644 --- a/Common/Server/Services/DashboardService.ts +++ b/Common/Server/Services/DashboardService.ts @@ -1,12 +1,27 @@ import CreateBy from "../Types/Database/CreateBy"; import { OnCreate } from "../Types/Database/Hooks"; +import CookieUtil from "../Utils/Cookie"; +import { ExpressRequest } from "../Utils/Express"; +import JSONWebToken from "../Utils/JsonWebToken"; +import logger from "../Utils/Logger"; import DatabaseService from "./DatabaseService"; import BadDataException from "../../Types/Exception/BadDataException"; +import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException"; +import ForbiddenException from "../../Types/Exception/ForbiddenException"; +import MasterPasswordRequiredException from "../../Types/Exception/MasterPasswordRequiredException"; import Model from "../../Models/DatabaseModels/Dashboard"; import { IsBillingEnabled } from "../EnvironmentConfig"; import { PlanType } from "../../Types/Billing/SubscriptionPlan"; import DashboardViewConfigUtil from "../../Utils/Dashboard/DashboardViewConfig"; import CaptureSpan from "../Utils/Telemetry/CaptureSpan"; +import ObjectID from "../../Types/ObjectID"; +import { JSONObject } from "../../Types/JSON"; +import IP from "../../Types/IP/IP"; +import { + DASHBOARD_MASTER_PASSWORD_COOKIE_IDENTIFIER, + DASHBOARD_MASTER_PASSWORD_REQUIRED_MESSAGE, +} from "../../Types/Dashboard/MasterPassword"; + export class Service extends DatabaseService { public constructor() { super(Model); @@ -46,6 +61,145 @@ export class Service extends DatabaseService { return Promise.resolve({ createBy, carryForward: null }); } + + public async hasReadAccess(data: { + dashboardId: ObjectID; + req: ExpressRequest; + }): Promise<{ + hasReadAccess: boolean; + error?: NotAuthenticatedException | ForbiddenException; + }> { + const dashboardId: ObjectID = data.dashboardId; + const req: ExpressRequest = data.req; + + try { + const dashboard: Model | null = await this.findOneById({ + id: dashboardId, + props: { + isRoot: true, + }, + select: { + _id: true, + isPublicDashboard: true, + ipWhitelist: true, + enableMasterPassword: true, + masterPassword: true, + }, + }); + + if (dashboard?.ipWhitelist && dashboard.ipWhitelist.length > 0) { + const ipWhitelist: Array = dashboard.ipWhitelist?.split("\n"); + + const ipAccessedFrom: string | undefined = + req.headers["x-forwarded-for"]?.toString() || + req.headers["x-real-ip"]?.toString() || + req.socket.remoteAddress || + req.ip || + req.ips[0]; + + if (!ipAccessedFrom) { + logger.error("IP address not found in request."); + return { + hasReadAccess: false, + error: new ForbiddenException( + "Unable to verify IP address for dashboard access.", + ), + }; + } + + const isIPWhitelisted: boolean = IP.isInWhitelist({ + ips: + ipAccessedFrom?.split(",").map((i: string) => { + return i.trim(); + }) || [], + whitelist: ipWhitelist, + }); + + if (!isIPWhitelisted) { + logger.error( + `IP address ${ipAccessedFrom} is not whitelisted for dashboard ${dashboardId.toString()}.`, + ); + + return { + hasReadAccess: false, + error: new ForbiddenException( + `Your IP address ${ipAccessedFrom} is blocked from accessing this dashboard.`, + ), + }; + } + } + + if (dashboard && dashboard.isPublicDashboard) { + return { + hasReadAccess: true, + }; + } + + const shouldEnforceMasterPassword: boolean = Boolean( + dashboard && + dashboard.enableMasterPassword && + dashboard.masterPassword && + !dashboard.isPublicDashboard, + ); + + if (shouldEnforceMasterPassword) { + const hasValidMasterPassword: boolean = + this.hasValidMasterPasswordCookie({ + req, + dashboardId, + }); + + if (hasValidMasterPassword) { + return { + hasReadAccess: true, + }; + } + + return { + hasReadAccess: false, + error: new MasterPasswordRequiredException( + DASHBOARD_MASTER_PASSWORD_REQUIRED_MESSAGE, + ), + }; + } + } catch (err) { + logger.error(err); + } + + return { + hasReadAccess: false, + error: new NotAuthenticatedException( + "You do not have access to this dashboard. Please login to view the dashboard.", + ), + }; + } + + private hasValidMasterPasswordCookie(data: { + req: ExpressRequest; + dashboardId: ObjectID; + }): boolean { + const token: string | undefined = CookieUtil.getCookieFromExpressRequest( + data.req, + CookieUtil.getDashboardMasterPasswordKey(data.dashboardId), + ); + + if (!token) { + return false; + } + + try { + const payload: JSONObject = JSONWebToken.decodeJsonPayload(token); + + return ( + payload["dashboardId"] === data.dashboardId.toString() && + payload["type"] === DASHBOARD_MASTER_PASSWORD_COOKIE_IDENTIFIER + ); + } catch (err) { + logger.error(err); + } + + return false; + } } export default new Service(); diff --git a/Common/Server/Services/Index.ts b/Common/Server/Services/Index.ts index 69436a8254..e33dc08eaa 100644 --- a/Common/Server/Services/Index.ts +++ b/Common/Server/Services/Index.ts @@ -105,6 +105,7 @@ import SpanService from "./SpanService"; import StatusPageAnnouncementService from "./StatusPageAnnouncementService"; import StatusPageAnnouncementTemplateService from "./StatusPageAnnouncementTemplateService"; import StatusPageCustomFieldService from "./StatusPageCustomFieldService"; +import DashboardDomainService from "./DashboardDomainService"; import StatusPageDomainService from "./StatusPageDomainService"; import StatusPageFooterLinkService from "./StatusPageFooterLinkService"; import StatusPageGroupService from "./StatusPageGroupService"; @@ -305,6 +306,7 @@ const services: Array = [ StatusPageAnnouncementService, StatusPageAnnouncementTemplateService, StatusPageCustomFieldService, + DashboardDomainService, StatusPageDomainService, StatusPageFooterLinkService, StatusPageGroupService, diff --git a/Common/Server/Services/WorkspaceNotificationSummaryService.ts b/Common/Server/Services/WorkspaceNotificationSummaryService.ts index 0fd9aca6e4..3f8c46885f 100644 --- a/Common/Server/Services/WorkspaceNotificationSummaryService.ts +++ b/Common/Server/Services/WorkspaceNotificationSummaryService.ts @@ -241,6 +241,9 @@ export class Service extends DatabaseService { items: Array, item: WorkspaceNotificationSummaryItem, ): boolean { + if (items.includes(WorkspaceNotificationSummaryItem.All)) { + return true; + } return items.includes(item); } @@ -822,6 +825,11 @@ export class Service extends DatabaseService { ackResolve.push( `Ack: ${Service.bold(td.ackBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || inc.createdAt!, td.ackAt))}`, ); + } else if (td?.resolvedBy && td?.resolvedAt) { + // If not explicitly acknowledged but resolved, ack time = resolve time + ackResolve.push( + `Ack: ${Service.bold(td.resolvedBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || inc.createdAt!, td.resolvedAt))}`, + ); } else { ackResolve.push(`_Not yet acknowledged_`); } @@ -1251,6 +1259,11 @@ export class Service extends DatabaseService { ackResolve.push( `Ack: ${Service.bold(td.ackBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || a.createdAt!, td.ackAt))}`, ); + } else if (td?.resolvedBy && td?.resolvedAt) { + // If not explicitly acknowledged but resolved, ack time = resolve time + ackResolve.push( + `Ack: ${Service.bold(td.resolvedBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || a.createdAt!, td.resolvedAt))}`, + ); } else { ackResolve.push(`_Not yet acknowledged_`); } @@ -1433,8 +1446,9 @@ export class Service extends DatabaseService { let total: number = 0; let count: number = 0; for (const [, td] of tlMap) { + // For ack: if not explicitly acknowledged but resolved, use resolve time as ack time const eventTime: Date | undefined = - kind === "ack" ? td.ackAt : td.resolvedAt; + kind === "ack" ? (td.ackAt || td.resolvedAt) : td.resolvedAt; if (eventTime && td.declaredAt) { total += OneUptimeDate.getMinutesBetweenTwoDates( td.declaredAt, diff --git a/Common/Server/Utils/Cookie.ts b/Common/Server/Utils/Cookie.ts index d0ff16f829..41487f6fa2 100644 --- a/Common/Server/Utils/Cookie.ts +++ b/Common/Server/Utils/Cookie.ts @@ -12,6 +12,10 @@ import { MASTER_PASSWORD_COOKIE_IDENTIFIER, MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS, } from "../../Types/StatusPage/MasterPassword"; +import { + DASHBOARD_MASTER_PASSWORD_COOKIE_IDENTIFIER, + DASHBOARD_MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS, +} from "../../Types/Dashboard/MasterPassword"; import CaptureSpan from "./Telemetry/CaptureSpan"; export default class CookieUtil { @@ -323,6 +327,50 @@ export default class CookieUtil { ); } + @CaptureSpan() + public static setDashboardMasterPasswordCookie(data: { + expressResponse: ExpressResponse; + dashboardId: ObjectID; + }): void { + const expiresInDays: PositiveNumber = new PositiveNumber( + DASHBOARD_MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS, + ); + + const token: string = JSONWebToken.signJsonPayload( + { + dashboardId: data.dashboardId.toString(), + type: DASHBOARD_MASTER_PASSWORD_COOKIE_IDENTIFIER, + }, + OneUptimeDate.getSecondsInDays(expiresInDays), + ); + + CookieUtil.setCookie( + data.expressResponse, + CookieUtil.getDashboardMasterPasswordKey(data.dashboardId), + token, + { + maxAge: OneUptimeDate.getMillisecondsInDays(expiresInDays), + httpOnly: true, + }, + ); + } + + @CaptureSpan() + public static removeDashboardMasterPasswordCookie( + res: ExpressResponse, + dashboardId: ObjectID, + ): void { + CookieUtil.removeCookie( + res, + CookieUtil.getDashboardMasterPasswordKey(dashboardId), + ); + } + + @CaptureSpan() + public static getDashboardMasterPasswordKey(id: ObjectID): string { + return `${CookieName.DashboardMasterPassword}-${id.toString()}`; + } + // get all cookies with express request @CaptureSpan() public static getAllCookies(req: ExpressRequest): Dictionary { diff --git a/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts b/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts index ac24ea9c4f..ea8e335bbe 100644 --- a/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +++ b/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts @@ -43,6 +43,10 @@ import URL from "../../../Types/API/URL"; import IP from "../../../Types/IP/IP"; import Hostname from "../../../Types/API/Hostname"; import Port from "../../../Types/Port"; +import MetricMonitorResponse, { + KubernetesAffectedResource, + KubernetesResourceBreakdown, +} from "../../../Types/Monitor/MetricMonitor/MetricMonitorResponse"; export default class MonitorCriteriaEvaluator { public static async processMonitorStep(input: { @@ -545,6 +549,11 @@ ${contextBlock} monitorStep: MonitorStep; monitor: Monitor; }): string | null { + // Handle Kubernetes monitors with rich resource context + if (input.monitor.monitorType === MonitorType.Kubernetes) { + return MonitorCriteriaEvaluator.buildKubernetesRootCauseContext(input); + } + const requestDetails: Array = []; const responseDetails: Array = []; const failureDetails: Array = []; @@ -653,6 +662,293 @@ ${contextBlock} return sections.join("\n"); } + private static buildKubernetesRootCauseContext(input: { + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + monitor: Monitor; + }): string | null { + const metricResponse: MetricMonitorResponse = + input.dataToProcess as MetricMonitorResponse; + + const breakdown: KubernetesResourceBreakdown | undefined = + metricResponse.kubernetesResourceBreakdown; + + if (!breakdown) { + return null; + } + + const sections: Array = []; + + // Cluster context + const clusterDetails: Array = []; + clusterDetails.push(`- Cluster: ${breakdown.clusterName}`); + clusterDetails.push(`- Metric: ${breakdown.metricFriendlyName} (\`${breakdown.metricName}\`)`); + + if (breakdown.attributes["k8s.namespace.name"]) { + clusterDetails.push( + `- Namespace: ${breakdown.attributes["k8s.namespace.name"]}`, + ); + } + + sections.push( + `**Kubernetes Cluster Details**\n${clusterDetails.join("\n")}`, + ); + + // Affected resources + if ( + breakdown.affectedResources && + breakdown.affectedResources.length > 0 + ) { + const resourceLines: Array = []; + + // Sort by metric value descending (worst first) + const sortedResources: Array = [ + ...breakdown.affectedResources, + ].sort( + ( + a: KubernetesAffectedResource, + b: KubernetesAffectedResource, + ) => { + return b.metricValue - a.metricValue; + }, + ); + + // Show top 10 affected resources + const resourcesToShow: Array = + sortedResources.slice(0, 10); + + for (const resource of resourcesToShow) { + const details: Array = []; + + if (resource.namespace) { + details.push(`Namespace: \`${resource.namespace}\``); + } + if (resource.workloadType && resource.workloadName) { + details.push( + `${resource.workloadType}: \`${resource.workloadName}\``, + ); + } + if (resource.podName) { + details.push(`Pod: \`${resource.podName}\``); + } + if (resource.containerName) { + details.push(`Container: \`${resource.containerName}\``); + } + if (resource.nodeName) { + details.push(`Node: \`${resource.nodeName}\``); + } + + details.push(`Value: **${resource.metricValue}**`); + + resourceLines.push(`- ${details.join(" | ")}`); + } + + if (sortedResources.length > 10) { + resourceLines.push( + `- ... and ${sortedResources.length - 10} more affected resources`, + ); + } + + sections.push( + `\n\n**Affected Resources** (${sortedResources.length} total)\n${resourceLines.join("\n")}`, + ); + + // Add root cause analysis based on metric type + const analysis: string | null = + MonitorCriteriaEvaluator.buildKubernetesRootCauseAnalysis({ + breakdown: breakdown, + topResource: resourcesToShow[0]!, + }); + + if (analysis) { + sections.push(`\n\n**Root Cause Analysis**\n${analysis}`); + } + } + + return sections.join("\n"); + } + + private static buildKubernetesRootCauseAnalysis(input: { + breakdown: KubernetesResourceBreakdown; + topResource: KubernetesAffectedResource; + }): string | null { + const { breakdown, topResource } = input; + const metricName: string = breakdown.metricName; + const lines: Array = []; + + if ( + metricName === "k8s.container.restarts" || + metricName.includes("restart") + ) { + lines.push( + `Container restart count is elevated, indicating a potential CrashLoopBackOff condition.`, + ); + if (topResource.containerName) { + lines.push( + `The container \`${topResource.containerName}\` in pod \`${topResource.podName || "unknown"}\` has restarted **${topResource.metricValue}** times.`, + ); + } + lines.push( + `Common causes: application crash on startup, misconfigured environment variables, missing dependencies, OOM (Out of Memory) kills, failed health checks, or missing config maps/secrets.`, + ); + lines.push( + `Recommended actions: Check container logs with \`kubectl logs ${topResource.podName || ""} -c ${topResource.containerName || ""} --previous\` and inspect events with \`kubectl describe pod ${topResource.podName || ""}\`.`, + ); + } else if ( + metricName === "k8s.pod.phase" && + breakdown.attributes["k8s.pod.phase"] === "Pending" + ) { + lines.push( + `Pods are stuck in Pending phase and unable to be scheduled.`, + ); + lines.push( + `Common causes: insufficient CPU/memory resources on nodes, node affinity/taint restrictions preventing scheduling, PersistentVolumeClaim pending, or resource quota exceeded.`, + ); + if (topResource.podName) { + lines.push( + `Recommended actions: Check scheduling events with \`kubectl describe pod ${topResource.podName}\` and verify node resources with \`kubectl describe nodes\`.`, + ); + } + } else if ( + metricName === "k8s.node.condition_ready" || + metricName.includes("node") && metricName.includes("condition") + ) { + lines.push(`One or more nodes have transitioned to a NotReady state.`); + if (topResource.nodeName) { + lines.push( + `Node \`${topResource.nodeName}\` is reporting NotReady (value: ${topResource.metricValue}).`, + ); + } + lines.push( + `Common causes: kubelet process failure, node resource exhaustion (disk pressure, memory pressure, PID pressure), network connectivity issues, or underlying VM/hardware failure.`, + ); + lines.push( + `Recommended actions: Check node conditions with \`kubectl describe node ${topResource.nodeName || ""}\` and verify kubelet status on the node.`, + ); + } else if ( + metricName === "k8s.node.cpu.utilization" || + metricName.includes("cpu") && metricName.includes("utilization") + ) { + lines.push(`Node CPU utilization has exceeded the configured threshold.`); + if (topResource.nodeName) { + lines.push( + `Node \`${topResource.nodeName}\` is at **${topResource.metricValue.toFixed(1)}%** CPU utilization.`, + ); + } + lines.push( + `Common causes: resource-intensive workloads, insufficient resource limits on pods, noisy neighbor pods consuming excessive CPU, or insufficient cluster capacity.`, + ); + lines.push( + `Recommended actions: Identify top CPU consumers with \`kubectl top pods --all-namespaces --sort-by=cpu\` and consider scaling the cluster or adjusting pod resource limits.`, + ); + } else if ( + metricName === "k8s.node.memory.usage" || + metricName.includes("memory") && metricName.includes("usage") + ) { + lines.push( + `Node memory utilization has exceeded the configured threshold.`, + ); + if (topResource.nodeName) { + lines.push( + `Node \`${topResource.nodeName}\` memory usage is at **${topResource.metricValue.toFixed(1)}%**.`, + ); + } + lines.push( + `Common causes: memory leaks in applications, insufficient memory limits on pods, too many pods scheduled on the node, or growing dataset sizes.`, + ); + lines.push( + `Recommended actions: Check memory consumers with \`kubectl top pods --all-namespaces --sort-by=memory\` and review pod memory limits. Consider scaling the cluster or adding nodes with more memory.`, + ); + } else if ( + metricName === "k8s.deployment.unavailable_replicas" || + metricName.includes("unavailable") + ) { + lines.push( + `Deployment has unavailable replicas, indicating a mismatch between desired and available replicas.`, + ); + if (topResource.workloadName) { + lines.push( + `${topResource.workloadType || "Deployment"} \`${topResource.workloadName}\` has **${topResource.metricValue}** unavailable replica(s).`, + ); + } + lines.push( + `Common causes: failed rolling update, image pull errors (wrong image tag or missing registry credentials), pod crash loops, insufficient cluster resources to schedule new pods, or PodDisruptionBudget blocking updates.`, + ); + lines.push( + `Recommended actions: Check deployment rollout status with \`kubectl rollout status deployment/${topResource.workloadName || ""}\` and inspect pod events.`, + ); + } else if ( + metricName === "k8s.job.failed_pods" || + metricName.includes("job") && metricName.includes("fail") + ) { + lines.push(`Kubernetes Job has failed pods.`); + if (topResource.workloadName) { + lines.push( + `Job \`${topResource.workloadName}\` has **${topResource.metricValue}** failed pod(s).`, + ); + } + lines.push( + `Common causes: application error or non-zero exit code, resource limits exceeded (OOMKilled), misconfigured command or arguments, missing environment variables, or timeout exceeded.`, + ); + lines.push( + `Recommended actions: Check job status with \`kubectl describe job ${topResource.workloadName || ""}\` and review pod logs for the failed pod(s).`, + ); + } else if ( + metricName === "k8s.node.filesystem.usage" || + metricName.includes("disk") || + metricName.includes("filesystem") + ) { + lines.push( + `Node disk/filesystem usage has exceeded the configured threshold.`, + ); + if (topResource.nodeName) { + lines.push( + `Node \`${topResource.nodeName}\` filesystem usage is at **${topResource.metricValue.toFixed(1)}%**.`, + ); + } + lines.push( + `Common causes: container image layers consuming disk space, excessive logging, large emptyDir volumes, or accumulation of unused container images.`, + ); + lines.push( + `Recommended actions: Clean up unused images with \`docker system prune\` or \`crictl rmi --prune\`, check for large log files, and review PersistentVolumeClaim usage.`, + ); + } else if ( + metricName === "k8s.daemonset.misscheduled_nodes" || + metricName.includes("daemonset") + ) { + lines.push( + `DaemonSet has misscheduled or unavailable nodes.`, + ); + if (topResource.workloadName) { + lines.push( + `DaemonSet \`${topResource.workloadName}\` has **${topResource.metricValue}** misscheduled node(s).`, + ); + } + lines.push( + `Common causes: node taints preventing scheduling, incorrect node selectors, or node affinity rules excluding certain nodes.`, + ); + lines.push( + `Recommended actions: Check DaemonSet status with \`kubectl describe daemonset ${topResource.workloadName || ""}\` and verify node labels and taints.`, + ); + } else { + // Generic Kubernetes context + lines.push( + `Kubernetes metric \`${metricName}\` (${breakdown.metricFriendlyName}) has breached the configured threshold.`, + ); + if (topResource.podName) { + lines.push(`Most affected pod: \`${topResource.podName}\``); + } + if (topResource.nodeName) { + lines.push(`Most affected node: \`${topResource.nodeName}\``); + } + lines.push( + `Recommended actions: Investigate the affected resources using \`kubectl describe\` and \`kubectl logs\` commands.`, + ); + } + + return lines.join("\n"); + } + private static getMonitorDestinationString(input: { monitorStep: MonitorStep; probeResponse: ProbeMonitorResponse | null; diff --git a/Common/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts b/Common/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts index a3335ebcbc..b7b182817c 100644 --- a/Common/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +++ b/Common/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts @@ -1023,12 +1023,41 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase { logger.debug("Sending message to Microsoft Teams with data:"); logger.debug(data); - const adaptiveCard: JSONObject = this.buildAdaptiveCardFromMessageBlocks({ - messageBlocks: data.workspaceMessagePayload.messageBlocks, - }); + // Teams adaptive cards have a ~28KB payload limit. + // Split message blocks into chunks of 40 to avoid hitting the limit. + const maxBlocksPerCard: number = 40; + const allMessageBlocks: Array = + data.workspaceMessagePayload.messageBlocks; - logger.debug("Adaptive card built successfully:"); - logger.debug(JSON.stringify(adaptiveCard, null, 2)); + const adaptiveCards: Array = []; + + if (allMessageBlocks.length <= maxBlocksPerCard) { + adaptiveCards.push( + this.buildAdaptiveCardFromMessageBlocks({ + messageBlocks: allMessageBlocks, + }), + ); + } else { + for ( + let i: number = 0; + i < allMessageBlocks.length; + i += maxBlocksPerCard + ) { + const chunk: Array = allMessageBlocks.slice( + i, + i + maxBlocksPerCard, + ); + adaptiveCards.push( + this.buildAdaptiveCardFromMessageBlocks({ + messageBlocks: chunk, + }), + ); + } + } + + logger.debug( + `Built ${adaptiveCards.length} adaptive card(s) from ${allMessageBlocks.length} message blocks`, + ); const workspaceChannelsToPostTo: Array = []; @@ -1122,18 +1151,24 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase { ); } - const thread: WorkspaceThread = await this.sendAdaptiveCardToChannel({ - authToken: data.authToken, - teamId: data.workspaceMessagePayload.teamId!, - workspaceChannel: channel, - adaptiveCard: adaptiveCard, - projectId: data.projectId, - }); + // Send each adaptive card chunk to the channel + let lastThread: WorkspaceThread | undefined; + for (const adaptiveCard of adaptiveCards) { + lastThread = await this.sendAdaptiveCardToChannel({ + authToken: data.authToken, + teamId: data.workspaceMessagePayload.teamId!, + workspaceChannel: channel, + adaptiveCard: adaptiveCard, + projectId: data.projectId, + }); + } - logger.debug( - `Message sent successfully to channel ${channel.name}, thread: ${JSON.stringify(thread)}`, - ); - workspaceMessageResponse.threads.push(thread); + if (lastThread) { + logger.debug( + `Message sent successfully to channel ${channel.name}, thread: ${JSON.stringify(lastThread)}`, + ); + workspaceMessageResponse.threads.push(lastThread); + } } catch (e) { logger.error(`Error sending message to channel ID ${channel.id}:`); logger.error(e); diff --git a/Common/Server/Utils/Workspace/Slack/Slack.ts b/Common/Server/Utils/Workspace/Slack/Slack.ts index 792c9474ac..c9fd697421 100644 --- a/Common/Server/Utils/Workspace/Slack/Slack.ts +++ b/Common/Server/Utils/Workspace/Slack/Slack.ts @@ -1129,13 +1129,37 @@ export default class SlackUtil extends WorkspaceBase { } } - const thread: WorkspaceThread = await this.sendPayloadBlocksToChannel({ - authToken: data.authToken, - workspaceChannel: channel, - blocks: blocks, - }); + // Slack has a limit of 50 blocks per message. Split into batches if needed. + const maxBlocksPerMessage: number = 50; + let lastThread: WorkspaceThread | undefined; - workspaspaceMessageResponse.threads.push(thread); + if (blocks.length <= maxBlocksPerMessage) { + lastThread = await this.sendPayloadBlocksToChannel({ + authToken: data.authToken, + workspaceChannel: channel, + blocks: blocks, + }); + } else { + for ( + let i: number = 0; + i < blocks.length; + i += maxBlocksPerMessage + ) { + const chunk: Array = blocks.slice( + i, + i + maxBlocksPerMessage, + ); + lastThread = await this.sendPayloadBlocksToChannel({ + authToken: data.authToken, + workspaceChannel: channel, + blocks: chunk, + }); + } + } + + if (lastThread) { + workspaspaceMessageResponse.threads.push(lastThread); + } logger.debug(`Message sent to channel ID ${channel.id} successfully.`); } catch (e) { diff --git a/Common/ServiceRoute.ts b/Common/ServiceRoute.ts index f269c0b0b6..11895bb265 100644 --- a/Common/ServiceRoute.ts +++ b/Common/ServiceRoute.ts @@ -35,3 +35,5 @@ export const RealtimeRoute: Route = new Route("/realtime/socket"); export const DocsRoute: Route = new Route("/docs"); export const StatusPageApiRoute: Route = new Route("/status-page-api"); + +export const PublicDashboardRoute: Route = new Route("/public-dashboard"); diff --git a/Common/Types/CookieName.ts b/Common/Types/CookieName.ts index 4634e0b188..e9a4e52e67 100644 --- a/Common/Types/CookieName.ts +++ b/Common/Types/CookieName.ts @@ -8,6 +8,7 @@ enum CookieName { IsMasterAdmin = "user-is-master-admin", ProfilePicID = "user-profile-pic-id", StatusPageMasterPassword = "status-page-master-password", + DashboardMasterPassword = "dashboard-master-password", } export default CookieName; diff --git a/Common/Types/Dashboard/Chart/ChartType.ts b/Common/Types/Dashboard/Chart/ChartType.ts index cee32db2de..94a846a4df 100644 --- a/Common/Types/Dashboard/Chart/ChartType.ts +++ b/Common/Types/Dashboard/Chart/ChartType.ts @@ -1,6 +1,11 @@ enum DashboardChartType { Line = "Line", Bar = "Bar", + Area = "Area", + StackedArea = "Stacked Area", + Pie = "Pie", + Heatmap = "Heatmap", + Histogram = "Histogram", } export default DashboardChartType; diff --git a/Common/Types/Dashboard/DashboardComponentType.ts b/Common/Types/Dashboard/DashboardComponentType.ts index 78787d3584..ae2bb142f5 100644 --- a/Common/Types/Dashboard/DashboardComponentType.ts +++ b/Common/Types/Dashboard/DashboardComponentType.ts @@ -2,6 +2,10 @@ enum DashboardComponentType { Chart = `Chart`, Value = `Value`, Text = `Text`, + Table = `Table`, + Gauge = `Gauge`, + LogStream = `LogStream`, + TraceList = `TraceList`, } export default DashboardComponentType; diff --git a/Common/Types/Dashboard/DashboardComponents/ComponentArgument.ts b/Common/Types/Dashboard/DashboardComponents/ComponentArgument.ts index a639877ffb..4e4b91f27b 100644 --- a/Common/Types/Dashboard/DashboardComponents/ComponentArgument.ts +++ b/Common/Types/Dashboard/DashboardComponents/ComponentArgument.ts @@ -9,6 +9,7 @@ export enum ComponentInputType { Number = "Number", Decimal = "Decimal", MetricsQueryConfig = "MetricsQueryConfig", + MetricsQueryConfigs = "MetricsQueryConfigs", LongText = "Long Text", Dropdown = "Dropdown", } diff --git a/Common/Types/Dashboard/DashboardComponents/DashboardChartComponent.ts b/Common/Types/Dashboard/DashboardComponents/DashboardChartComponent.ts index 8a077179a5..15b4a04704 100644 --- a/Common/Types/Dashboard/DashboardComponents/DashboardChartComponent.ts +++ b/Common/Types/Dashboard/DashboardComponents/DashboardChartComponent.ts @@ -4,15 +4,24 @@ import DashboardComponentType from "../DashboardComponentType"; import DashboardChartType from "../Chart/ChartType"; import BaseComponent from "./DashboardBaseComponent"; +export interface ChartThreshold { + value: number; + label?: string | undefined; + color?: string | undefined; +} + export default interface DashboardChartComponent extends BaseComponent { componentType: DashboardComponentType.Chart; componentId: ObjectID; arguments: { metricQueryConfig?: MetricQueryConfigData | undefined; + metricQueryConfigs?: Array | undefined; chartTitle?: string | undefined; chartDescription?: string | undefined; legendText?: string | undefined; legendUnit?: string | undefined; chartType?: DashboardChartType | undefined; + warningThreshold?: number | undefined; + criticalThreshold?: number | undefined; }; } diff --git a/Common/Types/Dashboard/DashboardComponents/DashboardGaugeComponent.ts b/Common/Types/Dashboard/DashboardComponents/DashboardGaugeComponent.ts new file mode 100644 index 0000000000..725f9ac6d4 --- /dev/null +++ b/Common/Types/Dashboard/DashboardComponents/DashboardGaugeComponent.ts @@ -0,0 +1,17 @@ +import MetricQueryConfigData from "../../Metrics/MetricQueryConfigData"; +import ObjectID from "../../ObjectID"; +import DashboardComponentType from "../DashboardComponentType"; +import BaseComponent from "./DashboardBaseComponent"; + +export default interface DashboardGaugeComponent extends BaseComponent { + componentType: DashboardComponentType.Gauge; + componentId: ObjectID; + arguments: { + metricQueryConfig?: MetricQueryConfigData | undefined; + gaugeTitle?: string | undefined; + minValue?: number | undefined; + maxValue?: number | undefined; + warningThreshold?: number | undefined; + criticalThreshold?: number | undefined; + }; +} diff --git a/Common/Types/Dashboard/DashboardComponents/DashboardLogStreamComponent.ts b/Common/Types/Dashboard/DashboardComponents/DashboardLogStreamComponent.ts new file mode 100644 index 0000000000..4d456c0573 --- /dev/null +++ b/Common/Types/Dashboard/DashboardComponents/DashboardLogStreamComponent.ts @@ -0,0 +1,15 @@ +import ObjectID from "../../ObjectID"; +import DashboardComponentType from "../DashboardComponentType"; +import BaseComponent from "./DashboardBaseComponent"; + +export default interface DashboardLogStreamComponent extends BaseComponent { + componentType: DashboardComponentType.LogStream; + componentId: ObjectID; + arguments: { + title?: string | undefined; + severityFilter?: string | undefined; + bodyContains?: string | undefined; + attributeFilterQuery?: string | undefined; + maxRows?: number | undefined; + }; +} diff --git a/Common/Types/Dashboard/DashboardComponents/DashboardTableComponent.ts b/Common/Types/Dashboard/DashboardComponents/DashboardTableComponent.ts new file mode 100644 index 0000000000..eabc863a7c --- /dev/null +++ b/Common/Types/Dashboard/DashboardComponents/DashboardTableComponent.ts @@ -0,0 +1,14 @@ +import MetricQueryConfigData from "../../Metrics/MetricQueryConfigData"; +import ObjectID from "../../ObjectID"; +import DashboardComponentType from "../DashboardComponentType"; +import BaseComponent from "./DashboardBaseComponent"; + +export default interface DashboardTableComponent extends BaseComponent { + componentType: DashboardComponentType.Table; + componentId: ObjectID; + arguments: { + metricQueryConfig?: MetricQueryConfigData | undefined; + tableTitle?: string | undefined; + maxRows?: number | undefined; + }; +} diff --git a/Common/Types/Dashboard/DashboardComponents/DashboardTextComponent.ts b/Common/Types/Dashboard/DashboardComponents/DashboardTextComponent.ts index 08cd73d6ce..d1a60c6633 100644 --- a/Common/Types/Dashboard/DashboardComponents/DashboardTextComponent.ts +++ b/Common/Types/Dashboard/DashboardComponents/DashboardTextComponent.ts @@ -10,5 +10,6 @@ export default interface DashboardTextComponent extends BaseComponent { isBold: boolean; isItalic: boolean; isUnderline: boolean; + isMarkdown?: boolean | undefined; }; } diff --git a/Common/Types/Dashboard/DashboardComponents/DashboardTraceListComponent.ts b/Common/Types/Dashboard/DashboardComponents/DashboardTraceListComponent.ts new file mode 100644 index 0000000000..7765927862 --- /dev/null +++ b/Common/Types/Dashboard/DashboardComponents/DashboardTraceListComponent.ts @@ -0,0 +1,13 @@ +import ObjectID from "../../ObjectID"; +import DashboardComponentType from "../DashboardComponentType"; +import BaseComponent from "./DashboardBaseComponent"; + +export default interface DashboardTraceListComponent extends BaseComponent { + componentType: DashboardComponentType.TraceList; + componentId: ObjectID; + arguments: { + title?: string | undefined; + statusFilter?: string | undefined; + maxRows?: number | undefined; + }; +} diff --git a/Common/Types/Dashboard/DashboardComponents/DashboardValueComponent.ts b/Common/Types/Dashboard/DashboardComponents/DashboardValueComponent.ts index 18d2036fc7..505b807ed9 100644 --- a/Common/Types/Dashboard/DashboardComponents/DashboardValueComponent.ts +++ b/Common/Types/Dashboard/DashboardComponents/DashboardValueComponent.ts @@ -9,5 +9,7 @@ export default interface DashboardValueComponent extends BaseComponent { arguments: { metricQueryConfig?: MetricQueryConfigData | undefined; title: string; + warningThreshold?: number | undefined; + criticalThreshold?: number | undefined; }; } diff --git a/Common/Types/Dashboard/DashboardVariable.ts b/Common/Types/Dashboard/DashboardVariable.ts new file mode 100644 index 0000000000..5c24970f06 --- /dev/null +++ b/Common/Types/Dashboard/DashboardVariable.ts @@ -0,0 +1,23 @@ +export enum DashboardVariableType { + CustomList = "Custom List", + Query = "Query", + TextInput = "Text Input", +} + +export default interface DashboardVariable { + id: string; + name: string; + label?: string | undefined; + type: DashboardVariableType; + // For CustomList: comma-separated values + customListValues?: string | undefined; + // For Query: a ClickHouse query to populate options + query?: string | undefined; + // Current selected value(s) + selectedValue?: string | undefined; + selectedValues?: Array | undefined; + // Whether multi-select is enabled + isMultiSelect?: boolean | undefined; + // Default value + defaultValue?: string | undefined; +} diff --git a/Common/Types/Dashboard/DashboardViewConfig.ts b/Common/Types/Dashboard/DashboardViewConfig.ts index 53ac71d12a..9fc2d0709f 100644 --- a/Common/Types/Dashboard/DashboardViewConfig.ts +++ b/Common/Types/Dashboard/DashboardViewConfig.ts @@ -1,8 +1,67 @@ import { ObjectType } from "../JSON"; import DashboardBaseComponent from "./DashboardComponents/DashboardBaseComponent"; +import DashboardVariable from "./DashboardVariable"; + +export enum AutoRefreshInterval { + OFF = "off", + FIVE_SECONDS = "5s", + TEN_SECONDS = "10s", + THIRTY_SECONDS = "30s", + ONE_MINUTE = "1m", + FIVE_MINUTES = "5m", + FIFTEEN_MINUTES = "15m", +} + +export function getAutoRefreshIntervalInMs( + interval: AutoRefreshInterval, +): number | null { + switch (interval) { + case AutoRefreshInterval.OFF: + return null; + case AutoRefreshInterval.FIVE_SECONDS: + return 5000; + case AutoRefreshInterval.TEN_SECONDS: + return 10000; + case AutoRefreshInterval.THIRTY_SECONDS: + return 30000; + case AutoRefreshInterval.ONE_MINUTE: + return 60000; + case AutoRefreshInterval.FIVE_MINUTES: + return 300000; + case AutoRefreshInterval.FIFTEEN_MINUTES: + return 900000; + default: + return null; + } +} + +export function getAutoRefreshIntervalLabel( + interval: AutoRefreshInterval, +): string { + switch (interval) { + case AutoRefreshInterval.OFF: + return "Off"; + case AutoRefreshInterval.FIVE_SECONDS: + return "5s"; + case AutoRefreshInterval.TEN_SECONDS: + return "10s"; + case AutoRefreshInterval.THIRTY_SECONDS: + return "30s"; + case AutoRefreshInterval.ONE_MINUTE: + return "1m"; + case AutoRefreshInterval.FIVE_MINUTES: + return "5m"; + case AutoRefreshInterval.FIFTEEN_MINUTES: + return "15m"; + default: + return "Off"; + } +} export default interface DashboardViewConfig { _type: ObjectType.DashboardViewConfig; components: Array; heightInDashboardUnits: number; + refreshInterval?: AutoRefreshInterval | undefined; + variables?: Array | undefined; } diff --git a/Common/Types/Dashboard/MasterPassword.ts b/Common/Types/Dashboard/MasterPassword.ts new file mode 100644 index 0000000000..0bdebe895f --- /dev/null +++ b/Common/Types/Dashboard/MasterPassword.ts @@ -0,0 +1,10 @@ +export const DASHBOARD_MASTER_PASSWORD_REQUIRED_MESSAGE: string = + "Master password required"; + +export const DASHBOARD_MASTER_PASSWORD_INVALID_MESSAGE: string = + "Invalid master password. Please try again."; + +export const DASHBOARD_MASTER_PASSWORD_COOKIE_IDENTIFIER: string = + "dashboard-master-password"; + +export const DASHBOARD_MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS: number = 7; diff --git a/Common/Types/Metrics/MetricQueryConfigData.ts b/Common/Types/Metrics/MetricQueryConfigData.ts index c9e6fc24ee..7fc33c92f8 100644 --- a/Common/Types/Metrics/MetricQueryConfigData.ts +++ b/Common/Types/Metrics/MetricQueryConfigData.ts @@ -5,6 +5,7 @@ import MetricQueryData from "./MetricQueryData"; export enum MetricChartType { LINE = "line", BAR = "bar", + AREA = "area", } export interface ChartSeries { diff --git a/Common/Types/Monitor/KubernetesAlertTemplates.ts b/Common/Types/Monitor/KubernetesAlertTemplates.ts index ce57638a68..dbf49deecf 100644 --- a/Common/Types/Monitor/KubernetesAlertTemplates.ts +++ b/Common/Types/Monitor/KubernetesAlertTemplates.ts @@ -88,9 +88,19 @@ export function buildOfflineCriteriaInstance(args: { metricAlias: string; filterType: FilterType; value: number; + incidentTitle?: string; + incidentDescription?: string; + criteriaName?: string; + criteriaDescription?: string; }): MonitorCriteriaInstance { const instance: MonitorCriteriaInstance = new MonitorCriteriaInstance(); + const incidentTitle: string = + args.incidentTitle || `${args.monitorName} - Alert Triggered`; + const incidentDescription: string = + args.incidentDescription || + `${args.monitorName} has triggered an alert condition. See root cause for detailed Kubernetes resource information.`; + instance.data = { id: ObjectID.generate().toString(), monitorStatusId: args.offlineMonitorStatusId, @@ -108,8 +118,8 @@ export function buildOfflineCriteriaInstance(args: { ], incidents: [ { - title: `${args.monitorName} - Alert Triggered`, - description: `${args.monitorName} has triggered an alert condition.`, + title: incidentTitle, + description: incidentDescription, incidentSeverityId: args.incidentSeverityId, autoResolveIncident: true, id: ObjectID.generate().toString(), @@ -118,8 +128,8 @@ export function buildOfflineCriteriaInstance(args: { ], alerts: [ { - title: `${args.monitorName} - Alert`, - description: `${args.monitorName} has triggered an alert condition.`, + title: incidentTitle, + description: incidentDescription, alertSeverityId: args.alertSeverityId, autoResolveAlert: true, id: ObjectID.generate().toString(), @@ -129,8 +139,9 @@ export function buildOfflineCriteriaInstance(args: { changeMonitorStatus: true, createIncidents: true, createAlerts: true, - name: `${args.monitorName} - Unhealthy`, - description: `Criteria for detecting unhealthy state.`, + name: args.criteriaName || `${args.monitorName} - Unhealthy`, + description: + args.criteriaDescription || `Criteria for detecting unhealthy state.`, }; return instance; @@ -239,6 +250,11 @@ const crashLoopBackOffTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 5, + incidentTitle: `[K8s] CrashLoopBackOff Detected - ${args.monitorName}`, + incidentDescription: `A container in the Kubernetes cluster is repeatedly crashing and restarting (CrashLoopBackOff). The container restart count has exceeded the threshold of 5 restarts. Check the root cause for the specific pod, container, and node details.`, + criteriaName: "CrashLoopBackOff - Container Restarts > 5", + criteriaDescription: + "Triggers when any container restart count exceeds 5 in the monitoring window, indicating a CrashLoopBackOff condition.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -268,7 +284,7 @@ const podPendingTemplate: KubernetesAlertTemplate = { resourceScope: KubernetesResourceScope.Cluster, rollingTime: RollingTime.Past5Minutes, aggregationType: MetricsAggregationType.Sum, - attributes: { "k8s.pod.phase": "Pending" }, + attributes: { "resource.k8s.pod.phase": "Pending" }, }), offlineCriteriaInstance: buildOfflineCriteriaInstance({ offlineMonitorStatusId: args.offlineMonitorStatusId, @@ -278,6 +294,11 @@ const podPendingTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 0, + incidentTitle: `[K8s] Pods Stuck in Pending - ${args.monitorName}`, + incidentDescription: `One or more pods in the Kubernetes cluster are stuck in Pending phase and cannot be scheduled. This typically indicates insufficient cluster resources, node affinity constraints, or unbound PersistentVolumeClaims. Check the root cause for specific pod and scheduling details.`, + criteriaName: "Pods Pending - Count > 0", + criteriaDescription: + "Triggers when any pods are in Pending phase, unable to be scheduled.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -316,6 +337,11 @@ const nodeNotReadyTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.EqualTo, value: 0, + incidentTitle: `[K8s] Node Not Ready - ${args.monitorName}`, + incidentDescription: `A Kubernetes node has transitioned to NotReady state. This is a critical condition that affects all pods scheduled on this node. Check the root cause for the specific node name, conditions, and recommended actions.`, + criteriaName: "Node NotReady - Condition = 0", + criteriaDescription: + "Triggers when any node reports a NotReady condition (value 0).", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -353,6 +379,11 @@ const highCpuTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 90, + incidentTitle: `[K8s] High CPU Utilization (>90%) - ${args.monitorName}`, + incidentDescription: `Node CPU utilization has exceeded 90% in the Kubernetes cluster. Sustained high CPU usage can cause pod throttling, increased latency, and potential node instability. Check the root cause for the specific node and top CPU-consuming workloads.`, + criteriaName: "High CPU - Utilization > 90%", + criteriaDescription: + "Triggers when average node CPU utilization exceeds 90% over the monitoring window.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -390,6 +421,11 @@ const highMemoryTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 85, + incidentTitle: `[K8s] High Memory Utilization (>85%) - ${args.monitorName}`, + incidentDescription: `Node memory utilization has exceeded 85% in the Kubernetes cluster. High memory usage can lead to OOMKilled pods, node instability, and potential evictions. Check the root cause for the specific node and top memory-consuming workloads.`, + criteriaName: "High Memory - Utilization > 85%", + criteriaDescription: + "Triggers when average node memory utilization exceeds 85% over the monitoring window.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -428,6 +464,11 @@ const deploymentReplicaMismatchTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 0, + incidentTitle: `[K8s] Deployment Replica Mismatch - ${args.monitorName}`, + incidentDescription: `A Kubernetes deployment has unavailable replicas — the desired replica count does not match the available count. This may indicate a failed rollout, image pull errors, insufficient resources, or pod crash loops. Check the root cause for the specific deployment and replica details.`, + criteriaName: "Replica Mismatch - Unavailable > 0", + criteriaDescription: + "Triggers when any deployment has unavailable replicas.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -465,6 +506,11 @@ const jobFailuresTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 0, + incidentTitle: `[K8s] Job Failure Detected - ${args.monitorName}`, + incidentDescription: `A Kubernetes Job has one or more failed pods. This indicates the job's workload is failing to complete successfully. Check the root cause for the specific job name, failed pod details, and error information.`, + criteriaName: "Job Failures - Failed Pods > 0", + criteriaDescription: + "Triggers when any Kubernetes Job has failed pods.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -503,6 +549,11 @@ const etcdNoLeaderTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.EqualTo, value: 0, + incidentTitle: `[K8s] CRITICAL: etcd No Leader - ${args.monitorName}`, + incidentDescription: `The etcd cluster has no elected leader. This is a critical cluster health issue that can cause the Kubernetes API server to become unavailable. All cluster operations (scheduling, deployments, service discovery) will be affected.`, + criteriaName: "etcd No Leader - Has Leader = 0", + criteriaDescription: + "Triggers immediately when etcd reports no elected leader.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -541,6 +592,11 @@ const apiServerThrottlingTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 0, + incidentTitle: `[K8s] CRITICAL: API Server Throttling - ${args.monitorName}`, + incidentDescription: `The Kubernetes API server is dropping requests due to throttling. This indicates the API server is overloaded and cannot process all incoming requests, affecting cluster operations.`, + criteriaName: "API Server Throttling - Dropped Requests > 0", + criteriaDescription: + "Triggers when the API server reports any dropped requests.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -579,6 +635,11 @@ const schedulerBacklogTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 0, + incidentTitle: `[K8s] Scheduler Backlog - ${args.monitorName}`, + incidentDescription: `The Kubernetes scheduler has a backlog of pods waiting to be scheduled. This indicates the scheduler is unable to find suitable nodes for pending pods, possibly due to resource constraints or scheduling conflicts.`, + criteriaName: "Scheduler Backlog - Pending Pods > 0", + criteriaDescription: + "Triggers when there are pods waiting to be scheduled for more than 5 minutes.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -616,6 +677,11 @@ const highDiskUsageTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 90, + incidentTitle: `[K8s] High Disk Usage (>90%) - ${args.monitorName}`, + incidentDescription: `Node disk/filesystem usage has exceeded 90% capacity. High disk usage can lead to pod evictions, inability to pull new container images, and node instability. Check the root cause for the specific node and disk usage details.`, + criteriaName: "High Disk - Usage > 90%", + criteriaDescription: + "Triggers when average node filesystem usage exceeds 90% capacity.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, @@ -654,6 +720,11 @@ const daemonSetUnavailableTemplate: KubernetesAlertTemplate = { metricAlias, filterType: FilterType.GreaterThan, value: 0, + incidentTitle: `[K8s] DaemonSet Unavailable Nodes - ${args.monitorName}`, + incidentDescription: `A DaemonSet has nodes where the daemon pod is not running as expected. This indicates misscheduled or unavailable daemon pods, which may affect cluster-wide services like logging, monitoring, or networking.`, + criteriaName: "DaemonSet Unavailable - Misscheduled > 0", + criteriaDescription: + "Triggers when a DaemonSet has nodes where daemon pods are not properly scheduled.", }), onlineCriteriaInstance: buildOnlineCriteriaInstance({ onlineMonitorStatusId: args.onlineMonitorStatusId, diff --git a/Common/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts b/Common/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts index f7cc6f1ac0..85b6bdd621 100644 --- a/Common/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +++ b/Common/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts @@ -3,6 +3,25 @@ import InBetween from "../../BaseDatabase/InBetween"; import MonitorEvaluationSummary from "../MonitorEvaluationSummary"; import MetricsViewConfig from "../../Metrics/MetricsViewConfig"; import ObjectID from "../../ObjectID"; +import Dictionary from "../../Dictionary"; + +export interface KubernetesAffectedResource { + podName?: string | undefined; + namespace?: string | undefined; + nodeName?: string | undefined; + containerName?: string | undefined; + workloadType?: string | undefined; + workloadName?: string | undefined; + metricValue: number; +} + +export interface KubernetesResourceBreakdown { + clusterName: string; + metricName: string; + metricFriendlyName: string; + affectedResources: Array; + attributes: Dictionary; +} export default interface MetricMonitorResponse { projectId: ObjectID; @@ -11,4 +30,5 @@ export default interface MetricMonitorResponse { metricViewConfig: MetricsViewConfig; monitorId: ObjectID; evaluationSummary?: MonitorEvaluationSummary | undefined; + kubernetesResourceBreakdown?: KubernetesResourceBreakdown | undefined; } diff --git a/Common/Types/Permission.ts b/Common/Types/Permission.ts index 50604ad2ba..1b7367c429 100644 --- a/Common/Types/Permission.ts +++ b/Common/Types/Permission.ts @@ -76,6 +76,12 @@ enum Permission { ReadDashboard = "ReadDashboard", EditDashboard = "EditDashboard", + // Dashboard Domains + CreateDashboardDomain = "CreateDashboardDomain", + DeleteDashboardDomain = "DeleteDashboardDomain", + EditDashboardDomain = "EditDashboardDomain", + ReadDashboardDomain = "ReadDashboardDomain", + // Logs CreateTelemetryServiceLog = "CreateTelemetryServiceLog", DeleteTelemetryServiceLog = "DeleteTelemetryServiceLog", @@ -1215,6 +1221,44 @@ export class PermissionHelper { group: PermissionGroup.Settings, }, + // Dashboard Domain permissions. + { + permission: Permission.CreateDashboardDomain, + title: "Create Dashboard Domain", + description: + "This permission can create Dashboard Domains of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + group: PermissionGroup.Settings, + }, + { + permission: Permission.DeleteDashboardDomain, + title: "Delete Dashboard Domain", + description: + "This permission can delete Dashboard Domains of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + group: PermissionGroup.Settings, + }, + { + permission: Permission.EditDashboardDomain, + title: "Edit Dashboard Domain", + description: + "This permission can edit Dashboard Domains of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + group: PermissionGroup.Settings, + }, + { + permission: Permission.ReadDashboardDomain, + title: "Read Dashboard Domain", + description: + "This permission can read Dashboard Domains of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + group: PermissionGroup.Settings, + }, + // Table view permissions { diff --git a/Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.ts b/Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.ts index eeea675588..32dd3bd54d 100644 --- a/Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.ts +++ b/Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.ts @@ -1,4 +1,5 @@ enum WorkspaceNotificationSummaryItem { + All = "All", TotalCount = "Total Count", ListWithLinks = "List with Links", WhoAcknowledged = "Who Acknowledged", diff --git a/Common/UI/Components/Charts/Area/AreaChart.tsx b/Common/UI/Components/Charts/Area/AreaChart.tsx index 75a3ad537b..ed9cdd6a6f 100644 --- a/Common/UI/Components/Charts/Area/AreaChart.tsx +++ b/Common/UI/Components/Charts/Area/AreaChart.tsx @@ -74,6 +74,7 @@ const AreaChartElement: FunctionComponent = ( curve={props.curve || ChartCurve.MONOTONE} syncid={props.sync ? props.syncid : undefined} yAxisWidth={60} + onValueChange={() => {}} /> ); }; diff --git a/Common/UI/Components/Charts/Bar/BarChart.tsx b/Common/UI/Components/Charts/Bar/BarChart.tsx index 13148faa40..97094bb1d4 100644 --- a/Common/UI/Components/Charts/Bar/BarChart.tsx +++ b/Common/UI/Components/Charts/Bar/BarChart.tsx @@ -69,6 +69,7 @@ const BarChartElement: FunctionComponent = ( showTooltip={true} yAxisWidth={60} syncid={props.sync ? props.syncid : undefined} + onValueChange={() => {}} /> ); }; diff --git a/Common/UI/Components/Charts/Line/LineChart.tsx b/Common/UI/Components/Charts/Line/LineChart.tsx index 2ca0a85896..6249a6dc63 100644 --- a/Common/UI/Components/Charts/Line/LineChart.tsx +++ b/Common/UI/Components/Charts/Line/LineChart.tsx @@ -73,6 +73,7 @@ const LineChartElement: FunctionComponent = ( curve={props.curve} syncid={props.sync ? props.syncid : undefined} yAxisWidth={60} + onValueChange={() => {}} /> ); }; diff --git a/Common/UI/Components/MoreMenu/MoreMenu.tsx b/Common/UI/Components/MoreMenu/MoreMenu.tsx index 608bf51646..1dc13ee74e 100644 --- a/Common/UI/Components/MoreMenu/MoreMenu.tsx +++ b/Common/UI/Components/MoreMenu/MoreMenu.tsx @@ -130,7 +130,7 @@ const MoreMenu: React.ForwardRefExoticComponent<