├── examples ├── published-folders │ ├── files │ │ └── new-file.txt │ ├── published-folders.png │ └── dashboard.ps1 ├── input-dynamic-pages.png ├── input │ ├── toast.ps1 │ ├── custom-help-text.ps1 │ ├── new-component.ps1 │ ├── input-types.ps1 │ └── dynamic-page.ps1 ├── service-table.ps1 ├── formatting │ ├── dynamic.ps1 │ ├── layout.ps1 │ └── rows-and-columns.ps1 ├── service-cards.ps1 ├── login-pages │ ├── microsoft.ps1 │ └── forms.ps1 ├── grid │ ├── simple.ps1 │ ├── custom-search.ps1 │ ├── custom-columns.ps1 │ └── high-performance-sql-grid.ps1 ├── charts │ ├── legend.ps1 │ ├── title.ps1 │ ├── custom-axis.ps1 │ ├── multiple-datasets.ps1 │ ├── tooltips.ps1 │ └── colors.ps1 ├── login-page.ps1 ├── ad-onboarding-dashboard.ps1 ├── ping-grid.ps1 ├── input-dynamic-pages.ps1 └── tree-view │ └── filesystem.ps1 ├── src ├── templates │ ├── Blank.ps1 │ ├── Multiple Pages.ps1 │ └── Rows and Columns.ps1 ├── favicon.ico ├── UniversalDashboard.psd1 ├── Scripts │ ├── interactive │ │ ├── Hide-Modal.ps1 │ │ ├── Hide-Toast.ps1 │ │ ├── Invoke-JavaScript.ps1 │ │ ├── Invoke-Redirect.ps1 │ │ ├── Select-Element.ps1 │ │ ├── Get-Element.ps1 │ │ ├── Clear-Element.ps1 │ │ ├── Set-Clipboard.ps1 │ │ ├── Start-Download.ps1 │ │ ├── Sync-Element.ps1 │ │ ├── Remove-Element.ps1 │ │ ├── Add-Element.ps1 │ │ ├── Set-Element.ps1 │ │ └── Show-Modal.ps1 │ ├── map │ │ ├── featuregroup.ps1 │ │ ├── layercontrol.ps1 │ │ ├── baselayer.ps1 │ │ ├── overlay.ps1 │ │ ├── cluster-layer.ps1 │ │ ├── icon.ps1 │ │ ├── heatmap.ps1 │ │ ├── popup.ps1 │ │ ├── rasterlayer.ps1 │ │ ├── marker.ps1 │ │ └── map.ps1 │ ├── iframe.ps1 │ ├── span.ps1 │ ├── editor.ps1 │ ├── event.ps1 │ ├── error.ps1 │ ├── errorboundary.ps1 │ ├── hidden.ps1 │ ├── splitpane.ps1 │ ├── paragraph.ps1 │ ├── container.ps1 │ ├── alert.ps1 │ ├── backdrop.ps1 │ ├── heading.ps1 │ ├── skeleton.ps1 │ ├── avatar.ps1 │ ├── style.ps1 │ ├── stack.ps1 │ ├── badge.ps1 │ ├── debugdashboard.ps1 │ ├── timepicker.ps1 │ ├── tooltip.ps1 │ ├── layout.ps1 │ ├── icon-button.ps1 │ ├── paper.ps1 │ ├── timeline.ps1 │ ├── datetime.ps1 │ ├── progress.ps1 │ └── drawer.ps1 ├── Components │ ├── framework │ │ ├── loading.jsx │ │ ├── not-authorized.jsx │ │ ├── togglecolormodes.jsx │ │ ├── not-found.jsx │ │ ├── not-running.jsx │ │ ├── ud-link.jsx │ │ ├── error-card.jsx │ │ └── ud-modal.jsx │ ├── table │ │ ├── v2 │ │ │ ├── Alert.js │ │ │ ├── EditableCell.js │ │ │ ├── table.js │ │ │ └── columns.js │ │ └── components │ │ │ ├── baseTableFooter.js │ │ │ └── baseTableHead.js │ ├── schema-form.jsx │ ├── grid.jsx │ ├── container.jsx │ ├── hidden.jsx │ ├── card-header.jsx │ ├── icon.jsx │ ├── map │ │ ├── utils.jsx │ │ ├── raster-layer.jsx │ │ ├── popup.jsx │ │ ├── MarkerCluster.css │ │ ├── index.js │ │ ├── MarkerCluster.Default.css │ │ ├── cluster-layer.jsx │ │ ├── heatmap-layer.jsx │ │ ├── UDLayerControlBaseLayer.jsx │ │ └── UDLayerControlOverlay.jsx │ ├── stack.jsx │ ├── avatar.jsx │ ├── alert.jsx │ ├── utilities.jsx │ ├── datetime.jsx │ ├── backdrop.jsx │ ├── style.jsx │ ├── badge.jsx │ ├── skeleton.jsx │ ├── charts │ │ ├── nivo-pie.jsx │ │ ├── nivo-line.jsx │ │ ├── nivo-bar.jsx │ │ ├── nivo-bubble.jsx │ │ ├── nivo-stream.jsx │ │ ├── nivo-heatmap.jsx │ │ ├── nivo-treemap.jsx │ │ ├── nivo-calendar.jsx │ │ ├── index.js │ │ └── sparklines.jsx │ ├── card-body.jsx │ ├── card-media.jsx │ ├── link.jsx │ ├── drawer.jsx │ ├── progress.jsx │ ├── paper.jsx │ ├── icon-button.jsx │ ├── slider.jsx │ ├── dynamic.jsx │ ├── radio.jsx │ ├── transition.jsx │ ├── button.jsx │ ├── timeline.jsx │ ├── floating-action-button.jsx │ ├── editor.jsx │ ├── switch.jsx │ ├── card-expand.jsx │ ├── datepicker.jsx │ ├── checkbox.jsx │ ├── timepicker.jsx │ ├── code-editor.jsx │ └── menu.jsx ├── app │ ├── ud-html.jsx │ ├── component-registration.js │ ├── app-context.jsx │ ├── public-path.js │ ├── log-service.jsx │ ├── index.jsx │ ├── services │ │ ├── toaster.jsx │ │ └── render-service.jsx │ ├── config.jsx │ ├── ud-tooltip.jsx │ ├── ud-icon.jsx │ ├── error-boundary.jsx │ ├── polyfills.js │ └── basics │ │ └── lazy-element.jsx ├── classes │ ├── classes.csproj │ ├── Cmdlets │ │ ├── NewNivoFillCommand.cs │ │ └── NewNivoChartAxisOptionsCommand.cs │ └── classes.sln ├── .babelrc ├── .gitignore └── index.html ├── images ├── logo.png └── splash.png ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .vscode └── tasks.json ├── CHANGELOG.md ├── azure-pipelines.yml ├── README.md └── docs └── working-on-a-component.md /examples/published-folders/files/new-file.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/Blank.ps1: -------------------------------------------------------------------------------- 1 | New-UDDashboard -Title 'PowerShell Universal' -Content { 2 | 3 | } -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-dashboard/HEAD/images/logo.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-dashboard/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-dashboard/HEAD/images/splash.png -------------------------------------------------------------------------------- /src/UniversalDashboard.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-dashboard/HEAD/src/UniversalDashboard.psd1 -------------------------------------------------------------------------------- /examples/input-dynamic-pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-dashboard/HEAD/examples/input-dynamic-pages.png -------------------------------------------------------------------------------- /src/Scripts/interactive/Hide-Modal.ps1: -------------------------------------------------------------------------------- 1 | function Hide-UDModal { 2 | $DashboardHub.SendWebSocketMessage($ConnectionId, "closeModal", $null) 3 | } -------------------------------------------------------------------------------- /examples/published-folders/published-folders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-dashboard/HEAD/examples/published-folders/published-folders.png -------------------------------------------------------------------------------- /src/Components/framework/loading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Loading extends React.Component { 4 | render() { 5 | return
6 | } 7 | } -------------------------------------------------------------------------------- /src/app/ud-html.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class UdHtml extends React.Component { 4 | render() { 5 | return
6 | } 7 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a Bug for Universal Dashboard 4 | labels: bug 5 | --- 6 | 7 | # Please file issues on the [PowerShell Universal](https://github.com/ironmansoftware/powershell-universal/issues) issue tracker. 8 | -------------------------------------------------------------------------------- /src/app/component-registration.js: -------------------------------------------------------------------------------- 1 | import UDSplitPane from './ud-splitpane'; 2 | import UDTooltip from './ud-tooltip'; 3 | require ('./../Components/index.js'); 4 | 5 | UniversalDashboard.register("ud-splitpane", UDSplitPane); 6 | UniversalDashboard.register("ud-tooltip", UDTooltip); 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Request 3 | about: Suggest an Enhancement for Universal Dashboard 4 | labels: enhancement 5 | --- 6 | 7 | # Please file issues on the [PowerShell Universal](https://github.com/ironmansoftware/powershell-universal/issues) issue tracker. 8 | -------------------------------------------------------------------------------- /src/app/app-context.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const theme = sessionStorage.getItem('theme') 4 | 5 | export const DefaultAppContext = { 6 | theme: theme ? theme : 'light', 7 | setTheme: () => {} 8 | } 9 | 10 | export const AppContext = React.createContext(DefaultAppContext); -------------------------------------------------------------------------------- /src/Components/table/v2/Alert.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Alert, AlertTitle } from '@mui/material'; 3 | 4 | export default function AlertCard({ error }) { 5 | return ( 6 | 7 | Error 8 | {error.message} 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/Components/schema-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MuiForm5 } from "@rjsf/material-ui"; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | 5 | function SchemaForm(props) { 6 | return 7 | } 8 | 9 | export default withComponentFeatures(SchemaForm); -------------------------------------------------------------------------------- /src/Components/grid.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Grid from '@mui/material/Grid'; 3 | 4 | const UDGrid = (props) => { 5 | return ( 6 | 7 | { props.children && UniversalDashboard.renderComponent(props.children)} 8 | 9 | ) 10 | } 11 | 12 | export default UDGrid; -------------------------------------------------------------------------------- /src/Components/container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Container from '@mui/material/Container'; 3 | import { withComponentFeatures } from 'universal-dashboard' 4 | 5 | const UDContainer = props => { 6 | return {props.render(props.children)} 7 | } 8 | 9 | export default withComponentFeatures(UDContainer) -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build and Test", 6 | "type": "shell", 7 | "command": "pwsh -Command ' & { Invoke-Build -File ${workspaceFolder}/src/build.ps1 }' ", 8 | "group": "build", 9 | "problemMatcher": [] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /src/Components/hidden.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Hidden from '@mui/material/Hidden'; 3 | 4 | const UDHidden = (props) => { 5 | return ( 6 | 7 | { props.children && UniversalDashboard.renderComponent(props.children)} 8 | 9 | ) 10 | } 11 | 12 | export default UDHidden; -------------------------------------------------------------------------------- /src/Components/card-header.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CardHeader from '@mui/material/CardHeader'; 3 | 4 | const UDCardHeader = (props) => { 5 | return ( 6 | 7 | ) 8 | } 9 | 10 | export default UDCardHeader; -------------------------------------------------------------------------------- /src/Scripts/interactive/Hide-Toast.ps1: -------------------------------------------------------------------------------- 1 | function Hide-UDToast { 2 | param( 3 | [Parameter(Mandatory, Position = 0)] 4 | [string]$Id 5 | ) 6 | 7 | if ($id -notmatch '^[a-zA-Z0-9]+$') { 8 | throw "Invalid ID. ID must be alphanumeric." 9 | } 10 | 11 | $DashboardHub.SendWebSocketMessage($ConnectionId, "hideToast", "x" + $Id) 12 | } 13 | -------------------------------------------------------------------------------- /src/classes/classes.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Components/table/v2/EditableCell.js: -------------------------------------------------------------------------------- 1 | import { Input } from '@mui/material' 2 | 3 | export default function EditableCell({ 4 | cell: { value }, 5 | row: { index }, 6 | column: { id }, 7 | updateData, 8 | inputProps, 9 | }) { 10 | const onChange = (event) => { 11 | updateData(index, id, event.target.value) 12 | } 13 | 14 | return 15 | } -------------------------------------------------------------------------------- /src/app/public-path.js: -------------------------------------------------------------------------------- 1 | if (document.getElementsByTagName('base')[0].href.indexOf("_BASEHREF_") == -1) 2 | { 3 | __webpack_public_path__ = document.getElementsByTagName('base')[0].href; 4 | window.baseUrl = document.getElementsByTagName('base')[0].href.replace(window.location.origin, "").replace(/\/$/, "");; 5 | } 6 | else 7 | { 8 | __webpack_public_path__ = "/" 9 | window.baseUrl = "" 10 | } -------------------------------------------------------------------------------- /examples/input/toast.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of returning a toast message to the user. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Input - Toast" -Content { 8 | New-UDInput -Title "Input" -Endpoint { 9 | param($Text) 10 | 11 | New-UDInputAction -Toast $Text -ClearInput 12 | } 13 | } 14 | 15 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/service-table.ps1: -------------------------------------------------------------------------------- 1 | Start-UDDashboard -Content { 2 | New-UDDashboard -Title "Server Performance Dashboard" -Color '#FF050F7F' -Content { 3 | New-UDTable -Title "Server Information" -Headers @("Name", "CommandLine", "Status") -Endpoint { 4 | Get-Service | Select Name,CommandLine,Status | Out-UDTableData -Property @("Name", "CommandLine", "Status") 5 | } 6 | } 7 | } -Port 1001 8 | 9 | -------------------------------------------------------------------------------- /examples/input/custom-help-text.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of providing custom help text on the input 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Input - Custom Help Text" -Content { 8 | New-UDInput -Title "Input" -Endpoint { 9 | param([Parameter(HelpMessage="Enter your text here")]$Text) 10 | } 11 | } 12 | 13 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/icon.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react' 3 | import classNames from "classnames" 4 | 5 | export default class UDIcon extends React.Component { 6 | render() { 7 | return UniversalDashboard.renderComponent({ 8 | className: classNames(this.props.className, "ud-mu-icon"), 9 | style: { ...this.props.style }, 10 | ...this.props, 11 | type: 'icon' 12 | }) 13 | } 14 | } -------------------------------------------------------------------------------- /src/Components/map/utils.jsx: -------------------------------------------------------------------------------- 1 | export function isGuid(str) { 2 | if (str == null) { return false } 3 | 4 | if (str[0] === "{") 5 | { 6 | str = str.substring(1, str.length - 1); 7 | } 8 | var regexGuid = /^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/gi; 9 | var regexGuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 10 | return regexGuid.test(str); 11 | } -------------------------------------------------------------------------------- /src/Components/stack.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Stack } from '@mui/material'; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | 5 | function UDStack(props) { 6 | 7 | const children = props.render(props.children); 8 | let divider = null; 9 | if (props.divider) { 10 | divider = props.render(props.divider) 11 | } 12 | 13 | return {children} 14 | } 15 | 16 | export default withComponentFeatures(UDStack); -------------------------------------------------------------------------------- /examples/input/new-component.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of returning a new component after taking input. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Input - New Component" -Content { 8 | New-UDInput -Title "Input" -Endpoint { 9 | param($Text) 10 | 11 | New-UDInputAction -Content @( 12 | New-UDCard -Title $Text -Text "This is text" 13 | ) 14 | } 15 | } 16 | 17 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/avatar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import withStyles from '@mui/styles/withStyles'; 4 | import Avatar from '@mui/material/Avatar'; 5 | import classNames from 'classnames' 6 | 7 | function UDMuAvatar(props) { 8 | 9 | const { classes } = props; 10 | 11 | return ( 12 | 13 | ); 14 | } 15 | 16 | export default UDMuAvatar; 17 | -------------------------------------------------------------------------------- /src/app/log-service.jsx: -------------------------------------------------------------------------------- 1 | import {fetchPost} from './services/fetch-service.jsx'; 2 | 3 | function Log(component, level, message) { 4 | fetchPost(`/log/${component}/${level}`, {message: message}) 5 | } 6 | 7 | export function LogDebug(component, message) { 8 | Log(component, "debug", message); 9 | } 10 | 11 | export function LogError(component, message) { 12 | Log(component, "error", message); 13 | } 14 | 15 | export function LogInfo(component, message) { 16 | Log(component, "info", message); 17 | } -------------------------------------------------------------------------------- /examples/input/input-types.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of the different type of input types. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Input - Input Types" -Content { 8 | New-UDInput -Title "Input" -Endpoint { 9 | param($Text, [bool]$CheckBox, [System.DayOfWeek]$SelectBox, [ValidateSet("One", "Two", "Three")]$SelectBox2) 10 | 11 | New-UDInputAction -Toast "Clicked!" 12 | } 13 | } 14 | 15 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/alert.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert, AlertTitle } from '@mui/material'; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | 5 | function UDAlert(props) { 6 | return 7 | {props.title && props.title !== "" ? {props.title} : } 8 | {props.render(props.children)} 9 | 10 | } 11 | 12 | export default withComponentFeatures(UDAlert); -------------------------------------------------------------------------------- /src/Scripts/map/featuregroup.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapFeatureGroup 2 | { 3 | param( 4 | [Parameter()] 5 | [string]$Id = (New-Guid).ToString(), 6 | [Parameter()] 7 | [Hashtable]$Popup, 8 | [Parameter(Mandatory)] 9 | [ScriptBlock]$Content 10 | ) 11 | 12 | End { 13 | @{ 14 | type = 'map-feature-group' 15 | id = $id 16 | isPlugin = $true 17 | assetId = $AssetId 18 | content = & $Content 19 | popup = $Popup 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Components/map/raster-layer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TileLayer } from 'react-leaflet'; 3 | import BingLayer from './bing-layer'; 4 | 5 | export default class UDRasterLayer extends React.Component { 6 | render() { 7 | if (this.props.bing) { 8 | return 9 | } else { 10 | return 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Components/utilities.jsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef} from 'react'; 2 | 3 | export function useTraceUpdate(props) { 4 | const prev = useRef(props); 5 | useEffect(() => { 6 | const changedProps = Object.entries(props).reduce((ps, [k, v]) => { 7 | if (prev.current[k] !== v) { 8 | ps[k] = [prev.current[k], v]; 9 | } 10 | return ps; 11 | }, {}); 12 | if (Object.keys(changedProps).length > 0) { 13 | console.log('Changed props:', changedProps); 14 | } 15 | prev.current = props; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/Scripts/map/layercontrol.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapLayerControl { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter()] 6 | [ValidateSet("topright", "topleft", "bottomright", "bottomleft")] 7 | [string]$Position = "topright", 8 | [Parameter()] 9 | [ScriptBlock]$Content 10 | ) 11 | 12 | @{ 13 | type = 'map-layer-control' 14 | isPlugin = $true 15 | assetId = $AssetId 16 | id = $Id 17 | content = & $Content 18 | position = $Position 19 | } 20 | } -------------------------------------------------------------------------------- /src/Scripts/map/baselayer.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapBaseLayer { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter(Mandatory)] 6 | [string]$Name, 7 | [Parameter(Mandatory)] 8 | [ScriptBlock]$Content, 9 | [Parameter()] 10 | [Switch]$Checked 11 | ) 12 | 13 | @{ 14 | type = "map-base-layer" 15 | isPlugin = $true 16 | assetId = $AssetId 17 | id = $Id 18 | name = $Name 19 | content = & $Content 20 | checked = $Checked.IsPresent 21 | } 22 | } -------------------------------------------------------------------------------- /src/Scripts/map/overlay.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapOverlay { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter(Mandatory)] 6 | [string]$Name, 7 | [Parameter(Mandatory)] 8 | [ScriptBlock]$Content, 9 | [Parameter()] 10 | [Switch]$Checked 11 | ) 12 | 13 | @{ 14 | type = 'map-overlay' 15 | isPlugin = $true 16 | assetId = $AssetId 17 | 18 | id = $id 19 | name = $Name 20 | content = & $Content 21 | checked = $Checked.IsPresent 22 | } 23 | } -------------------------------------------------------------------------------- /examples/formatting/dynamic.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an of example of using a dynamic column. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Formatting - Layout" -Content { 8 | New-UDRow -Columns { 9 | New-UDColumn -Size 12 -AutoRefresh -RefreshInterval 5 -Endpoint { 10 | $Max = Get-Random -Minimum 1 -Maximum 12 11 | 12 | 1..$Max | ForEach-Object { 13 | New-UDCard -Title "Card $_" 14 | } 15 | } 16 | 17 | } 18 | } 19 | 20 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/formatting/layout.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of using a simple layout. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Formatting - Layout" -Content { 8 | New-UDLayout -Columns 3 -Content { 9 | New-UDCard -Title "Card 1" 10 | New-UDCard -Title "Card 2" 11 | New-UDCard -Title "Card 3" 12 | New-UDCard -Title "Card 4" 13 | New-UDCard -Title "Card 5" 14 | New-UDCard -Title "Card 6" 15 | New-UDCard -Title "Card 7" 16 | } 17 | } 18 | 19 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | ">1.0%", 9 | "not dead", 10 | "IE 11" 11 | ] 12 | } 13 | } 14 | ], 15 | "babel-preset-react-app" 16 | ], 17 | "plugins": [ 18 | [ 19 | "minify-dead-code-elimination", 20 | { 21 | "optimizeRawSize": true 22 | } 23 | ], 24 | "@babel/plugin-proposal-class-properties", 25 | "@babel/plugin-syntax-dynamic-import", 26 | "@babel/plugin-transform-arrow-functions" 27 | ] 28 | } -------------------------------------------------------------------------------- /src/Scripts/map/cluster-layer.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapMarkerClusterLayer { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter()] 6 | [Hashtable[]]$Markers, 7 | [Parameter()] 8 | [int]$MinimumClusterSize = 2, 9 | [Parameter()] 10 | [int]$GridSize = 60 11 | ) 12 | 13 | @{ 14 | type = "map-cluster-layer" 15 | isPlugin = $true 16 | assetId = $assetId 17 | 18 | id = $id 19 | markers = $Markers 20 | minClusterSize = $MinimumClusterSize 21 | gridSize = $GridSize 22 | } 23 | } -------------------------------------------------------------------------------- /examples/service-cards.ps1: -------------------------------------------------------------------------------- 1 | $Dashboard = New-UDDashboard -Title Services -Content { 2 | New-UDRow -Endpoint { 3 | Get-Service | ForEach-Object { 4 | New-UDColumn -Size 2 -Content { 5 | $BackgroundColor = "Red" 6 | if ($_.Status -eq 'Running') { 7 | $BackgroundColor = "Green" 8 | } 9 | 10 | New-UDCard -Title $_.Name -Text $_.Status.ToString() -BackgroundColor $BackgroundColor -FontColor 'white' 11 | } 12 | } 13 | } -AutoRefresh -RefreshInterval 60 14 | } 15 | 16 | Start-UDDashboard -Dashboard $Dashboard -Port 10000 -------------------------------------------------------------------------------- /examples/login-pages/microsoft.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of a Microsoft Account login page. 3 | #> 4 | Import-Module UniversalDashboard 5 | 6 | $MicrosoftLogin = New-UDAuthenticationMethod -AppId "xyz" -AppSecret "123" -Provider Microsoft 7 | 8 | $LoginPage = New-UDLoginPage -AuthenticationMethod @($MicrosoftLogin) 9 | 10 | $MyDashboardPage = New-UDPage -Url "/myDashboard" -Endpoint { 11 | New-UDCard -Title "Welcome, $User" -Text "This is your custom dashboard." 12 | } 13 | 14 | $Dashboard = New-UDDashboard -LoginPage $LoginPage -Page @( 15 | $MyDashboardPage 16 | ) 17 | 18 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/grid/simple.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of a simple grid. 3 | #> 4 | Import-Module UniversalDashboard 5 | 6 | $Data = @( 7 | [PSCustomObject]@{Animal="Frog";Order="Anura"} 8 | [PSCustomObject]@{Animal="Tiger";Order="Carnivora"} 9 | [PSCustomObject]@{Animal="Bat";Order="Chiroptera"} 10 | [PSCustomObject]@{Animal="Fox";Order="Carnivora"} 11 | ) 12 | 13 | $Dashboard = New-UDDashboard -Title "Grids - Simple" -Content { 14 | New-UDGrid -Title "Animals" -Headers @("Animal", "Order") -Properties @("Animal", "Order") -Endpoint { 15 | $Data | Out-UDGridData 16 | } 17 | } 18 | 19 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/datetime.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | var dayjs = require('dayjs') 3 | var localizedFormat = require('dayjs/plugin/localizedFormat') 4 | var relativeTime = require('dayjs/plugin/relativeTime') 5 | var utc = require('dayjs/plugin/utc') 6 | dayjs.extend(relativeTime) 7 | dayjs.extend(utc) 8 | dayjs.extend(localizedFormat) 9 | 10 | const DayTime = (props) => { 11 | if (props.format.indexOf("L") === -1 && props.format.indexOf("l") === -1) { 12 | return dayjs(props.inputObject).format(props.format); 13 | } 14 | else { 15 | return dayjs(props.inputObject).local().format(props.format); 16 | } 17 | } 18 | 19 | export default DayTime; 20 | 21 | -------------------------------------------------------------------------------- /src/Scripts/iframe.ps1: -------------------------------------------------------------------------------- 1 | function New-UDIFrame { 2 | <# 3 | .SYNOPSIS 4 | An HTML IFrame component. 5 | 6 | .DESCRIPTION 7 | An HTML IFrame component. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Uri 13 | The URI for the iframe. 14 | 15 | .EXAMPLE 16 | Defines an IFrame for Google. 17 | 18 | New-UDIFrame -Uri https://www.google.com 19 | #> 20 | param( 21 | [Parameter()] 22 | [String]$Id = ([Guid]::NewGuid()), 23 | [Parameter()] 24 | $Uri 25 | ) 26 | 27 | New-UDElement -Id $Id -Tag "iframe" -Attributes @{ 28 | src = $Uri 29 | } 30 | } -------------------------------------------------------------------------------- /src/Scripts/interactive/Invoke-JavaScript.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-UDJavaScript { 2 | <# 3 | .SYNOPSIS 4 | Invokes JavaScript within the browser. 5 | 6 | .DESCRIPTION 7 | Invokes JavaScript within the browser. JavaScript is executed with eval() 8 | 9 | .PARAMETER JavaScript 10 | The JavaScript to execute. 11 | 12 | .EXAMPLE 13 | New-UDButton -Text 'Click Me' -OnClick { 14 | Invoke-UDJavaScript 'alert("Hello World!")' 15 | } 16 | #> 17 | param( 18 | [Parameter(Mandatory)] 19 | [string]$JavaScript 20 | ) 21 | 22 | $DashboardHub.SendWebSocketMessage($ConnectionId, "invokejavascript", $JavaScript) 23 | } -------------------------------------------------------------------------------- /examples/input/dynamic-page.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of redirecting to a dynamic page after taking input. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $HomePage = New-UDPage -Name "Home" -Content { 8 | New-UDInput -Title "Input" -Endpoint { 9 | param($Text) 10 | 11 | New-UDInputAction -RedirectUrl "/dynamic/$text" 12 | } 13 | } 14 | 15 | $DynamicPage = New-UDPage -Url "/dynamic/:text" -Endpoint { 16 | param($text) 17 | 18 | New-UDCard -Title $text 19 | } 20 | 21 | $Dashboard = New-UDDashboard -Title "Input - Dynamic Page" -Pages @( 22 | $HomePage 23 | $DynamicPage 24 | ) 25 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/backdrop.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Backdrop } from '@mui/material'; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | import makeStyles from '@mui/styles/makeStyles'; 5 | 6 | const useStyles = makeStyles((theme) => ({ 7 | backdrop: props => ({ 8 | zIndex: theme.zIndex.drawer + 1, 9 | color: props.color, 10 | }), 11 | })); 12 | 13 | 14 | function UDBackdrop(props) { 15 | const classes = useStyles(props); 16 | 17 | return props.onClick()}>{props.render(props.children)} 18 | } 19 | 20 | export default withComponentFeatures(UDBackdrop); -------------------------------------------------------------------------------- /src/Components/style.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css } from 'emotion'; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | import { Box } from '@mui/material'; 5 | 6 | const UDStyle = (props) => { 7 | var children = props.content; 8 | if (!Array.isArray(children)) { 9 | children = [children]; 10 | } 11 | 12 | children = children.map(x => props.render(x)); 13 | 14 | if (props.sx) { 15 | return {children} 16 | } 17 | 18 | const style = css(props.style); 19 | return React.createElement(props.tag, { className: style }, children); 20 | } 21 | 22 | export default withComponentFeatures(UDStyle); -------------------------------------------------------------------------------- /src/Components/badge.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Badge } from '@mui/material'; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | 5 | const UDBadge = (props) => { 6 | return 14 | {props.render(props.children)} 15 | 16 | } 17 | 18 | export default withComponentFeatures(UDBadge); -------------------------------------------------------------------------------- /src/Scripts/span.ps1: -------------------------------------------------------------------------------- 1 | function New-UDSpan { 2 | <# 3 | .SYNOPSIS 4 | A span component. 5 | 6 | .DESCRIPTION 7 | A span component. Defines a span HTML tag. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Content 13 | The content of the span. 14 | 15 | .EXAMPLE 16 | An example 17 | 18 | New-UDSpan -Content { 19 | New-UDTypography -Text 'Text' 20 | } 21 | #> 22 | param( 23 | [Parameter()] 24 | [String]$Id = ([Guid]::NewGuid()), 25 | [Parameter()] 26 | $Content 27 | ) 28 | 29 | New-UDElement -Id $Id -Tag "span" -Content { 30 | $Content 31 | } 32 | } -------------------------------------------------------------------------------- /src/app/index.jsx: -------------------------------------------------------------------------------- 1 | import './public-path'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import 'whatwg-fetch'; 5 | import Promise from 'promise-polyfill'; 6 | import { UniversalDashboardService } from './services/universal-dashboard-service.jsx'; 7 | import App from './App'; 8 | 9 | window.react = require('react'); 10 | window['reactdom'] = require('react-dom'); 11 | window['reactrouterdom'] = require('react-router-dom'); 12 | 13 | // To add to window 14 | if (!window.Promise) { 15 | window.Promise = Promise; 16 | } 17 | 18 | window.UniversalDashboard = UniversalDashboardService; 19 | require('./component-registration'); 20 | 21 | render(, document.getElementById('app')); -------------------------------------------------------------------------------- /src/Components/framework/not-authorized.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Typography from '@mui/material/Typography'; 3 | import UDIcon from '../icon'; 4 | import { withComponentFeatures } from 'universal-dashboard'; 5 | 6 | const NotAuthorized = (props) => { 7 | const [comp, setComponent] = React.useState(null); 8 | React.useEffect(() => { 9 | if (props.notAuthorized) { 10 | props.notAuthorized().then(json => setComponent(json)); 11 | } 12 | return () => { } 13 | }, []) 14 | 15 | if (comp === null) return <> 16 | var element = props.render(JSON.parse(comp)); 17 | return element; 18 | } 19 | 20 | export default withComponentFeatures(NotAuthorized); -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## v2.9.1-beta2 8 | 9 | ### Changed 10 | 11 | - Added UDGrid, UDLayout, UDModal, UDLink Tests 12 | - Fixed *-UDElement cmdlets 13 | - Added dependency on Universal module 14 | - Fixed custom component loading 15 | 16 | ## v3.0.0-beta3 17 | 18 | ### Changed 19 | 20 | - Added dependency on Universal module 21 | - Fixed custom component loading 22 | - Updated demo dashboard 23 | - Removed styles that made paper and buttons look weird 24 | -------------------------------------------------------------------------------- /src/Scripts/editor.ps1: -------------------------------------------------------------------------------- 1 | function New-UDEditor { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter()] 6 | [Hashtable]$Data, 7 | [Parameter()] 8 | [Endpoint]$OnChange, 9 | [Parameter()] 10 | [ValidateSet("json", "html")] 11 | [string]$Format = "json" 12 | ) 13 | 14 | if ($OnChange) { 15 | $OnChange.Register($Id, $PSCmdlet); 16 | } 17 | 18 | @{ 19 | assetId = $AssetId 20 | isPlugin = $true 21 | type = "ud-editor" 22 | id = $Id 23 | 24 | onChange = $OnChange 25 | data = $Data 26 | format = $Format.ToLower() 27 | } 28 | } -------------------------------------------------------------------------------- /src/Scripts/map/icon.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapIcon { 2 | param( 3 | [Parameter(Mandatory)] 4 | [string]$Url, 5 | [Parameter()] 6 | [int]$Height, 7 | [Parameter()] 8 | [int]$Width, 9 | [Parameter()] 10 | [int]$AnchorX, 11 | [Parameter()] 12 | [int]$AnchorY, 13 | [Parameter()] 14 | [int]$PopupAnchorX, 15 | [Parameter()] 16 | [int]$PopupAnchorY 17 | ) 18 | 19 | $Options = @{ 20 | } 21 | 22 | foreach($boundParameter in $PSCmdlet.MyInvocation.BoundParameters.GetEnumerator()) { 23 | $Options[[char]::ToLowerInvariant($boundParameter.Key[0]) + $boundParameter.Key.Substring(1)] = $boundParameter.Value 24 | } 25 | 26 | $Options 27 | } -------------------------------------------------------------------------------- /src/Components/skeleton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Skeleton } from '@mui/material'; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | 5 | function UDSkeleton(props) { 6 | 7 | var animation = null; 8 | if (props.animation === "disabled") { 9 | animation = false; 10 | } 11 | 12 | if (props.animation === "wave") { 13 | animation = 'wave'; 14 | } 15 | 16 | return 23 | } 24 | 25 | export default withComponentFeatures(UDSkeleton); -------------------------------------------------------------------------------- /src/app/services/toaster.jsx: -------------------------------------------------------------------------------- 1 | import iziToast from 'izitoast/dist/js/iziToast.min.js' 2 | require('izitoast/dist/css/iziToast.min.css') 3 | require('@fortawesome/fontawesome-free/css/all.min.css') 4 | 5 | const toaster = { 6 | show: (model) => { 7 | 8 | 9 | iziToast.show(model); 10 | }, 11 | hide: (id) => { 12 | var toast = document.querySelector('#' + id); 13 | iziToast.hide({}, toast); 14 | }, 15 | info: (model) => { 16 | iziToast.info(model); 17 | }, 18 | success: (model) => { 19 | iziToast.success(model); 20 | }, 21 | warning: (model) => { 22 | iziToast.warning(model); 23 | }, 24 | error: (model) => { 25 | iziToast.error(model); 26 | } 27 | } 28 | 29 | export default toaster; -------------------------------------------------------------------------------- /src/Scripts/event.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-UDEvent { 2 | param( 3 | [Parameter( 4 | Mandatory = $true, 5 | ValueFromPipeline = $true, 6 | Position = 0 7 | )] 8 | [String]$Id, 9 | [Parameter( 10 | Mandatory = $true, 11 | Position = 1, 12 | ParameterSetName = "onClick" 13 | )] 14 | [ValidateSet("onClick")] 15 | [string]$event 16 | ) 17 | 18 | Begin { 19 | 20 | } 21 | 22 | Process { 23 | if ($PSCmdlet.ParameterSetName -eq "onClick") { 24 | Invoke-UDJavaScript -javaScript " 25 | document.getElementById('$Id').click(); 26 | " 27 | } 28 | } 29 | 30 | End { 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Scripts/error.ps1: -------------------------------------------------------------------------------- 1 | function New-UDError { 2 | <# 3 | .SYNOPSIS 4 | Creates a new error card. 5 | 6 | .DESCRIPTION 7 | Creates a new error card. 8 | 9 | .PARAMETER Message 10 | The message to display. 11 | 12 | .PARAMETER Title 13 | A title for the card. 14 | 15 | .EXAMPLE 16 | Displays the error 'Invalid data' on the page. 17 | 18 | New-UDError -Message 'Invalid data' 19 | #> 20 | param( 21 | [Parameter(Mandatory)] 22 | [string]$Message, 23 | [Parameter()] 24 | [string]$Title 25 | ) 26 | 27 | @{ 28 | type = "error" 29 | isPlugin = $true 30 | assetId = $AssetId 31 | 32 | errorRecords = @(@{message= $Message}) 33 | title = $Title 34 | } 35 | } -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | pool: 7 | vmImage: 'vs2017-win2016' 8 | 9 | steps: 10 | 11 | - powershell: Install-Module Universal -Scope CurrentUser -Force 12 | displayName: Install Universal 13 | errorActionPreference: continue 14 | 15 | - powershell: Install-Module Selenium -Scope CurrentUser -Force 16 | displayName: Install Selenium 17 | errorActionPreference: continue 18 | 19 | - powershell: Install-Module InvokeBuild -Scope CurrentUser -Force 20 | displayName: Install InvokeBuild 21 | errorActionPreference: continue 22 | 23 | - powershell: Invoke-Build -File .\src\build.ps1 24 | displayName: Build 25 | errorActionPreference: continue -------------------------------------------------------------------------------- /src/Components/map/popup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Popup } from 'react-leaflet'; 3 | 4 | export default class UDPopup extends React.Component { 5 | render() { 6 | 7 | var content = null; 8 | if (Array.isArray(this.props.content)) { 9 | content = this.props.content.map(x => UniversalDashboard.renderComponent(x)); 10 | } else { 11 | content = UniversalDashboard.renderComponent(this.props.content); 12 | } 13 | 14 | var position = null; 15 | if (this.props.latitude && this.props.longitude) { 16 | position = [this.props.latitude, this.props.longitude]; 17 | } 18 | 19 | return 20 | {content} 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-pie.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ResponsivePie, Pie } from '@nivo/pie'; 3 | 4 | export default class NivoPie extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | 17 | render() { 18 | if (this.props.responsive) { 19 | return
20 | } else { 21 | return 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-line.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Line, ResponsiveLine } from '@nivo/line'; 3 | 4 | export default class NivoLine extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | 17 | render() { 18 | 19 | if (this.props.responsive) { 20 | return
21 | } else { 22 | return 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Scripts/errorboundary.ps1: -------------------------------------------------------------------------------- 1 | function New-UDErrorBoundary { 2 | <# 3 | .SYNOPSIS 4 | Defines a new error boundary around a section of code. 5 | 6 | .DESCRIPTION 7 | Defines a new error boundary around a section of code. Error boundaries are used to trap errors and display them on the page. 8 | 9 | .PARAMETER Content 10 | The content to trap in an error boundary. 11 | 12 | .EXAMPLE 13 | Defines an error boundary that traps the exception that is thrown and displays it on the page. 14 | 15 | New-UDErrorBoundary -Content { 16 | throw 'This is an error' 17 | } 18 | #> 19 | param( 20 | [Parameter(Mandatory)] 21 | [ScriptBlock]$Content 22 | ) 23 | 24 | try 25 | { 26 | & $Content 27 | } 28 | catch 29 | { 30 | New-UDError -Message $_ 31 | } 32 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-bar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ResponsiveBar, Bar } from '@nivo/bar'; 3 | 4 | export default class NivoBar extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | render() { 17 | if (this.props.responsive) { 18 | return
19 | } else { 20 | return 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-bubble.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ResponsiveBubble, Bubble } from '@nivo/circle-packing'; 3 | 4 | export default class NivoBubble extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | render() { 17 | if (this.props.responsive) { 18 | return
19 | } else { 20 | return 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-stream.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Stream, ResponsiveStream } from '@nivo/stream'; 3 | 4 | export default class NivoStream extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | 17 | render() { 18 | 19 | if (this.props.responsive) { 20 | return
21 | } else { 22 | return 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-heatmap.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { HeatMap, ResponsiveHeatMap } from '@nivo/heatmap'; 3 | 4 | export default class NivoHeatmap extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | render() { 17 | 18 | 19 | if (this.props.responsive) { 20 | return
21 | } else { 22 | return 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-treemap.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TreeMap, ResponsiveTreeMap } from '@nivo/treemap'; 3 | 4 | export default class NivoTreemap extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | 17 | render() { 18 | 19 | if (this.props.responsive) { 20 | return
21 | } else { 22 | return 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Components/charts/nivo-calendar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Calendar, ResponsiveCalendar } from '@nivo/calendar'; 3 | 4 | export default class NivoCalendar extends React.Component { 5 | onClick(e) { 6 | if (this.props.hasCallback) { 7 | UniversalDashboard.publish('element-event', { 8 | type: "clientEvent", 9 | eventId: this.props.id, 10 | eventName: 'onClick', 11 | eventData: JSON.stringify(e) 12 | }); 13 | } 14 | } 15 | 16 | 17 | render() { 18 | 19 | if (this.props.responsive) { 20 | return
21 | } else { 22 | return 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /examples/charts/legend.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of customizing the legend. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Data = @( 8 | @{Animal="Frog";Count=10} 9 | @{Animal="Tiger";Count=1} 10 | @{Animal="Bat";Count=34} 11 | @{Animal="Fox";Count=20} 12 | ) 13 | 14 | $Dashboard = New-UDDashboard -Title "Charts - Legend" -Content { 15 | 16 | $LegendOptions = New-UDChartLegendOptions -Position bottom 17 | $Options = New-UDLineChartOptions -LegendOptions $LegendOptions 18 | 19 | New-UDChart -Title "Bottom Legend" -Type "Line" -Endpoint { 20 | $Data | Out-UDChartData -LabelProperty "Animal" -Dataset @( 21 | New-UDLineChartDataset -Label "Animals" -DataProperty Count -BackgroundColor "#205D4CFF" -BorderColor "#5D4CFF" -BorderWidth 3 22 | ) 23 | } -Options $Options 24 | } 25 | 26 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Scripts/interactive/Invoke-Redirect.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-UDRedirect { 2 | <# 3 | .SYNOPSIS 4 | Redirect the user to another page. 5 | 6 | .DESCRIPTION 7 | Redirect the user to another page. 8 | 9 | .PARAMETER Url 10 | The URL to redirect the user to. 11 | 12 | .PARAMETER OpenInNewWindow 13 | Whether to open the URL in a new window. 14 | 15 | .EXAMPLE 16 | New-UDButton -Text 'Click Me' -OnClick { 17 | Invoke-UDRedirect 'https://www.google.com' 18 | } 19 | #> 20 | param( 21 | [Parameter(Mandatory)] 22 | [string]$Url, 23 | [Parameter()] 24 | [Switch]$OpenInNewWindow 25 | ) 26 | 27 | $Data = @{ 28 | url = $Url 29 | openInNewWindow = $OpenInNewWindow.IsPresent 30 | } 31 | 32 | $DashboardHub.SendWebSocketMessage($ConnectionId, "redirect", $Data) 33 | } -------------------------------------------------------------------------------- /src/templates/Multiple Pages.ps1: -------------------------------------------------------------------------------- 1 | $Navigation = @( 2 | New-UDListItem -Label 'Home' -Icon (New-UDIcon -Icon Home) -OnClick { 3 | Invoke-UDRedirect -Url '/home' 4 | } 5 | New-UDListItem -Label 'Users' -Icon (New-UDIcon -Icon User) -OnClick { 6 | Invoke-UDRedirect -Url '/users' 7 | } 8 | New-UDListItem -Label 'Groups' -Icon (New-UDIcon -Icon Users) -OnClick { 9 | Invoke-UDRedirect -Url '/groups' 10 | } 11 | ) 12 | 13 | $HomePage = New-UDPage -Name 'Home' -Content { 14 | New-UDTypography -Text 'Home' 15 | } 16 | 17 | $Users = New-UDPage -Name 'Users' -Content { 18 | New-UDTypography -Text 'Users' 19 | } 20 | 21 | $Groups = New-UDPage -Name 'Groups' -Content { 22 | New-UDTypography -Text 'Groups' 23 | } 24 | 25 | New-UDDashboard -Title 'PowerShell Universal' -Pages @( 26 | $HomePage 27 | $Users 28 | $Groups 29 | ) -Navigation $Navigation -------------------------------------------------------------------------------- /src/Components/framework/togglecolormodes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import IconButton from '@mui/material/IconButton'; 3 | import DarkIcon from '@mui/icons-material/Brightness7'; 4 | import LightIcon from '@mui/icons-material/Brightness4'; 5 | import { AppContext } from './../../app/app-context'; 6 | 7 | export default props => { 8 | return ( 9 | 10 | {context => { 11 | return ( 12 | { 15 | const next = context.theme === 'dark' ? 'light' : 'dark' 16 | localStorage.setItem('theme', next); 17 | context.setTheme(next) 18 | }} 19 | size="large"> 20 | {context.theme === 'dark' ? : } 21 | 22 | ); 23 | 24 | }} 25 | 26 | ); 27 | } -------------------------------------------------------------------------------- /examples/charts/title.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of customizing a chart's title. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Data = @( 8 | @{Animal="Frog";Count=10} 9 | @{Animal="Tiger";Count=1} 10 | @{Animal="Bat";Count=34} 11 | @{Animal="Fox";Count=20} 12 | ) 13 | 14 | $Dashboard = New-UDDashboard -Title "Charts - Title" -Content { 15 | 16 | $LegendOptions = New-UDChartTitleOptions -Display -Text "Animals" -FontSize 50 17 | $Options = New-UDLineChartOptions -TitleOptions $LegendOptions 18 | 19 | New-UDChart -Title "Chart with title" -Type "Line" -Endpoint { 20 | $Data | Out-UDChartData -LabelProperty "Animal" -Dataset @( 21 | New-UDLineChartDataset -Label "Animals" -DataProperty Count -BackgroundColor "#205D4CFF" -BorderColor "#5D4CFF" -BorderWidth 3 22 | ) 23 | } -Options $Options 24 | } 25 | 26 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/login-pages/forms.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of a forms login page. 3 | #> 4 | Import-Module UniversalDashboard 5 | 6 | $FormLogin = New-UDAuthenticationMethod -Endpoint { 7 | param([PSCredential]$Credentials) 8 | 9 | if ($Credentials.UserName -eq "Adam" -and $Credentials.GetNetworkCredential().Password -eq "SuperSecretPassword") { 10 | New-UDAuthenticationResult -Success -UserName "Adam" 11 | } 12 | 13 | New-UDAuthenticationResult -ErrorMessage $Credentials.GetNetworkCredential().Password 14 | } 15 | 16 | $LoginPage = New-UDLoginPage -AuthenticationMethod @($FormLogin) 17 | 18 | $MyDashboardPage = New-UDPage -Url "/myDashboard" -Endpoint { 19 | New-UDCard -Title "Welcome, $User" -Text "This is your custom dashboard." 20 | } 21 | 22 | $Dashboard = New-UDDashboard -LoginPage $LoginPage -Page @( 23 | $MyDashboardPage 24 | ) 25 | 26 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/grid/custom-search.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of creating a grid with a custom filtering algorithm. 3 | #> 4 | Import-Module UniversalDashboard 5 | 6 | $Data = @( 7 | [PSCustomObject]@{Animal="Frog";Order="Anura"} 8 | [PSCustomObject]@{Animal="Tiger";Order="Carnivora"} 9 | [PSCustomObject]@{Animal="Bat";Order="Chiroptera"} 10 | [PSCustomObject]@{Animal="Fox";Order="Carnivora"} 11 | ) 12 | 13 | $Dashboard = New-UDDashboard -Title "Grids - Simple" -Content { 14 | New-UDGrid -Title "Animals" -Headers @("Animal", "Order") -Properties @("Animal", "Order") -Endpoint { 15 | #By default, filtering checks all properties. This overrides it to only check the animal property. 16 | #$filterText is always provided in a grid endpoint 17 | $Data | Where-Object Animal -Match $filterText | Out-UDGridData -NoAutoFilter 18 | } 19 | } 20 | 21 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/card-body.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { CardContent } from "@mui/material"; 3 | import withStyles from '@mui/styles/withStyles'; 4 | import classNames from "classnames"; 5 | 6 | const styles = theme => ({ 7 | content: { 8 | //display: "flex" 9 | } 10 | }); 11 | 12 | export class UDCardBody extends React.Component { 13 | render() { 14 | const { 15 | className, 16 | classes, 17 | id, 18 | style 19 | } = this.props; 20 | 21 | return ( 22 | 23 | 28 | {UniversalDashboard.renderComponent(this.props.content)} 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | export default withStyles(styles)(UDCardBody); 36 | -------------------------------------------------------------------------------- /src/Components/card-media.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames" 3 | import makeStyles from '@mui/styles/makeStyles'; 4 | import { CardMedia } from "@mui/material"; 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | media: { 8 | objectFit: "cover", 9 | backgroundColor: "transparent" 10 | // height: "100%" 11 | } 12 | })); 13 | 14 | 15 | const UDCardMedia = (props) => { 16 | const classes = useStyles(); 17 | const { component, alt, height, image, title, source } = props; 18 | 19 | return ( 20 | 30 | ); 31 | } 32 | 33 | export default UDCardMedia; 34 | -------------------------------------------------------------------------------- /examples/charts/custom-axis.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of creating a custom axis with a minimum and maximum value for the axis. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Data = @( 8 | @{Animal="Frog";Count=10} 9 | @{Animal="Tiger";Count=1} 10 | @{Animal="Bat";Count=34} 11 | @{Animal="Fox";Count=20} 12 | ) 13 | 14 | $Dashboard = New-UDDashboard -Title "Charts - Custom Axis" -Content { 15 | 16 | $MinMaxAxis = New-UDLinearChartAxis -Minimum 10 -Maximum 20 17 | $Options = New-UDLineChartOptions -yAxes $MinMaxAxis 18 | 19 | New-UDChart -Title "Line Chart" -Type "Line" -Endpoint { 20 | $Data | Out-UDChartData -LabelProperty "Animal" -Dataset @( 21 | New-UDLineChartDataset -Label "Animals" -DataProperty Count -BackgroundColor "#205D4CFF" -BorderColor "#5D4CFF" -BorderWidth 3 22 | ) 23 | } -Options $Options 24 | } 25 | 26 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/charts/multiple-datasets.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provies an example of creating a chart with multiple datasets. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Data = @( 8 | @{Animal="Frog";Count=10;Babies=3} 9 | @{Animal="Tiger";Count=1;Babies=0} 10 | @{Animal="Bat";Count=34;Babies=30} 11 | @{Animal="Fox";Count=20;Babies=10} 12 | ) 13 | 14 | $Dashboard = New-UDDashboard -Title "Charts - Multiple Datasets" -Content { 15 | New-UDChart -Title "Bottom Legend" -Type "Bar" -Endpoint { 16 | $Data | Out-UDChartData -LabelProperty "Animal" -Dataset @( 17 | New-UDBarChartDataset -Label "Adults" -DataProperty Count -BackgroundColor "#205D4CFF" -BorderColor "#5D4CFF" -BorderWidth 3 18 | New-UDBarChartDataset -Label "Babies" -DataProperty Babies -BackgroundColor "#20A07DFF" -BorderColor "#A07DFF" -BorderWidth 3 19 | ) 20 | } 21 | } 22 | 23 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/map/MarkerCluster.css: -------------------------------------------------------------------------------- 1 | .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow { 2 | -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in; 3 | -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in; 4 | -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in; 5 | transition: transform 0.3s ease-out, opacity 0.3s ease-in; 6 | } 7 | 8 | .leaflet-cluster-spider-leg { 9 | /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */ 10 | -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in; 11 | -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in; 12 | -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in; 13 | transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in; 14 | } 15 | -------------------------------------------------------------------------------- /src/Scripts/map/heatmap.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapHeatmapLayer { 2 | param( 3 | [Parameter(Mandatory)] 4 | $Points, 5 | [Parameter()] 6 | [string]$Id = (New-Guid).ToString(), 7 | [Parameter()] 8 | [double]$MaxIntensity, 9 | [Parameter()] 10 | [double]$Radius, 11 | [Parameter()] 12 | [int]$MaxZoom, 13 | [Parameter()] 14 | [double]$MinOpacity, 15 | [Parameter()] 16 | [int]$Blur, 17 | [Parameter()] 18 | [Hashtable]$Gradient 19 | ) 20 | 21 | $Options = @{ 22 | type = 'map-heatmap-layer' 23 | isPlugin = $true 24 | assetId = $AssetId 25 | } 26 | 27 | foreach($boundParameter in $PSCmdlet.MyInvocation.BoundParameters.GetEnumerator()) { 28 | $Options[[char]::ToLowerInvariant($boundParameter.Key[0]) + $boundParameter.Key.Substring(1)] = $boundParameter.Value 29 | } 30 | 31 | $Options 32 | } -------------------------------------------------------------------------------- /src/Components/framework/not-found.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Typography from '@mui/material/Typography'; 3 | import UDIcon from '../icon'; 4 | import { withComponentFeatures } from 'universal-dashboard'; 5 | 6 | const NotFound = (props) => { 7 | const [comp, setComponent] = React.useState(null); 8 | React.useEffect(() => { 9 | if (props.pageNotFound) { 10 | props.pageNotFound().then(json => setComponent(json)); 11 | } 12 | return () => { } 13 | }, []) 14 | 15 | if (props.pageNotFound) { 16 | if (comp === null) return <> 17 | var element = props.render(JSON.parse(comp)); 18 | return element; 19 | } else { 20 | return
21 | Page Not Found 22 |
23 | } 24 | } 25 | 26 | export default withComponentFeatures(NotFound); -------------------------------------------------------------------------------- /src/app/config.jsx: -------------------------------------------------------------------------------- 1 | export function getApiPath() { 2 | return ""; 3 | } 4 | 5 | export function getWsApiPath() { 6 | var protocol = "ws://"; 7 | if (window.location.protocol.toLowerCase().startsWith("https")) { 8 | protocol = "wss://"; 9 | } 10 | 11 | return protocol + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); 12 | } 13 | 14 | function getMeta(metaName) { 15 | const metas = document.getElementsByTagName('meta'); 16 | 17 | for (let i = 0; i < metas.length; i++) { 18 | if (metas[i].getAttribute('name') === metaName) { 19 | return metas[i].getAttribute('content'); 20 | } 21 | } 22 | 23 | return ''; 24 | } 25 | 26 | export function getDashboardId() { 27 | var dashboardId = localStorage.getItem("ud-dashboard"); 28 | if (!dashboardId || dashboardId === '') 29 | { 30 | dashboardId = getMeta('ud-dashboard'); 31 | } 32 | 33 | return dashboardId; 34 | } -------------------------------------------------------------------------------- /examples/login-page.ps1: -------------------------------------------------------------------------------- 1 | Import-Module UniversalDashboard 2 | 3 | $FormLogin = New-UDAuthenticationMethod -Endpoint { 4 | param([PSCredential]$Credentials) 5 | 6 | if ($Credentials.UserName -eq "Adam" -and $Credentials.GetNetworkCredential().Password -eq "SuperSecretPassword") { 7 | New-UDAuthenticationResult -Success -UserName "Adam" 8 | } 9 | 10 | New-UDAuthenticationResult -ErrorMessage $Credentials.GetNetworkCredential().Password 11 | } 12 | 13 | $MicrosoftLogin = New-UDAuthenticationMethod -AppId "xyz" -AppSecret "123" -Provider Microsoft 14 | 15 | $LoginPage = New-UDLoginPage -AuthenticationMethod @($FormLogin, $MicrosoftLogin) 16 | 17 | $MyDashboardPage = New-UDPage -Url "/myDashboard" -Endpoint { 18 | New-UDCard -Title "Welcome, $User" -Text "This is your custom dashboard." 19 | } 20 | 21 | $Dashboard = New-UDDashboard -LoginPage $LoginPage -Page @( 22 | $MyDashboardPage 23 | ) 24 | 25 | Start-UDDashboard -Dashboard $Dashboard -Port 10000 26 | -------------------------------------------------------------------------------- /src/Components/link.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from '@mui/material/Link'; 3 | import classNames from 'classnames' 4 | import { withComponentFeatures } from 'universal-dashboard' 5 | 6 | const UDLink = (props) => { 7 | const { 8 | id, 9 | url, 10 | underline, 11 | style, 12 | variant, 13 | className, 14 | openInNewWindow, 15 | content, 16 | text, 17 | onClick 18 | } = props; 19 | 20 | return ( 21 | { 31 | if (onClick) { 32 | e.preventDefault(); 33 | onClick(); 34 | } 35 | }}> 36 | {!text ? (UniversalDashboard.renderComponent(content)) : text} 37 | 38 | ) 39 | } 40 | 41 | export default withComponentFeatures(UDLink); 42 | -------------------------------------------------------------------------------- /src/Components/framework/not-running.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Typography from '@mui/material/Typography'; 3 | import UDIcon from '../icon'; 4 | import { Redirect } from 'react-router-dom' 5 | 6 | const NotRunning = () => { 7 | 8 | const [redirect, setRedirect] = React.useState(false); 9 | 10 | React.useEffect(() => { 11 | var token = setInterval(() => { 12 | UniversalDashboard.get('/api/internal/dashboard', function (json) { 13 | setRedirect(true); 14 | }); 15 | }, 5000); 16 | 17 | return () => { clearInterval(token) } 18 | }, [true]); 19 | 20 | if (redirect) { 21 | return 22 | } 23 | 24 | return ( 25 |
26 | Dashboard is not running 27 |
28 | ) 29 | } 30 | 31 | export default NotRunning; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Universal Dashboard 2 | 3 | # Universal Dashboard is now a part of PowerShell Universal. Please submit issues on the [PowerShell Universal](https://github.com/ironmansoftware/powershell-universal) repository. 4 | 5 | Create beautiful, interactive websites with PowerShell. Universal Dashboard is part of [PowerShell Universal](https://ironmansoftware.com/powershell-universal/). 6 | 7 | ![](/images/splash.png) 8 | 9 | ## Resources 10 | 11 | - [Documentation](https://docs.ironmansoftware.com) 12 | - [Licensing](https://store.ironmansoftware.com/pricing/powershell-universal) 13 | - [Live Preview](https://poshud.com/) 14 | - [YouTube Videos](https://www.youtube.com/playlist?list=PL-0mHH7DlSiSZ4ozleNTUSXNkF6dlySVz) 15 | - [Forums](https://forums.universaldashboard.io/) 16 | 17 | ## Install 18 | 19 | Learn how to get started on our [documentation](https://docs.ironmansoftware.com/get-started). 20 | 21 | ## Examples 22 | 23 | [Examples](https://docs.ironmansoftware.com/examples) 24 | -------------------------------------------------------------------------------- /src/Scripts/map/popup.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapPopup { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter()] 6 | [ScriptBlock]$Content, 7 | [Parameter()] 8 | [float]$Longitude, 9 | [Parameter()] 10 | [float]$Latitude, 11 | [Parameter()] 12 | [int]$MaxWidth, 13 | [Parameter()] 14 | [int]$MinWidth 15 | ) 16 | 17 | $Options = @{ 18 | type = "map-popup" 19 | isPlugin = $true 20 | assetId = $AssetId 21 | 22 | id = $id 23 | content = & $Content 24 | 25 | maxWidth = $MaxWidth 26 | minWidth = $MinWidth 27 | } 28 | 29 | if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Longitude")) { 30 | $Options["longitude"] = $Longitude 31 | } 32 | 33 | if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Latitude")) { 34 | $Options["latitude"] = $Latitude 35 | } 36 | 37 | $Options 38 | } -------------------------------------------------------------------------------- /src/classes/Cmdlets/NewNivoFillCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management.Automation; 3 | 4 | namespace UniversalDashboard.Enterprise.Cmdlets 5 | { 6 | [Cmdlet(VerbsCommon.New, "UDNivoFill")] 7 | public class NewNivoFillCommand : PSCmdlet 8 | { 9 | private readonly Dictionary _values = new Dictionary(); 10 | 11 | [Parameter(Mandatory = true)] 12 | public string ElementId { get; set; } 13 | 14 | [Alias("GradientId")] 15 | [Parameter(Mandatory = true)] 16 | public string PatternId { get; set; } 17 | 18 | protected override void BeginProcessing() 19 | { 20 | WriteObject(new Dictionary 21 | { 22 | { "match", new Dictionary { 23 | { "id", ElementId } 24 | } 25 | }, 26 | { "id", PatternId } 27 | }); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Scripts/interactive/Select-Element.ps1: -------------------------------------------------------------------------------- 1 | function Select-UDElement { 2 | <# 3 | .SYNOPSIS 4 | Selects the specified element. 5 | 6 | .DESCRIPTION 7 | Selects the specified element. This cmdlet is useful for selecting input fields. 8 | 9 | .PARAMETER Id 10 | The ID of the element to select. 11 | 12 | .PARAMETER ScrollToElement 13 | Whether to scroll to the element. 14 | 15 | .EXAMPLE 16 | New-UDTextbox -Id 'txtName' -Label 'Name' 17 | 18 | New-UDButton -Text 'Click Me' -OnClick { 19 | Select-UDElement -Id 'txtName' 20 | } 21 | #> 22 | param( 23 | [Parameter(Mandatory, ParameterSetName = "Normal")] 24 | [string]$Id, 25 | [Parameter(ParameterSetName = "Normal")] 26 | [Switch]$ScrollToElement 27 | ) 28 | 29 | $Data = @{ 30 | id = $Id 31 | scrollToElement = $ScrollToElement 32 | } 33 | 34 | $DashboardHub.SendWebSocketMessage($ConnectionId, "select", $Data) 35 | } -------------------------------------------------------------------------------- /examples/grid/custom-columns.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of using custom columns. 3 | #> 4 | Import-Module UniversalDashboard 5 | 6 | $Data = @( 7 | [PSCustomObject]@{Animal="Frog";Order="Anura";Article=(New-UDLink -Text "Wikipedia" -Url "https://en.wikipedia.org/wiki/Frog")} 8 | [PSCustomObject]@{Animal="Tiger";Order="Carnivora";Article=(New-UDLink -Text "Wikipedia" -Url "https://en.wikipedia.org/wiki/Tiger")} 9 | [PSCustomObject]@{Animal="Bat";Order="Chiroptera";Article=(New-UDLink -Text "Wikipedia" -Url "https://en.wikipedia.org/wiki/Bat")} 10 | [PSCustomObject]@{Animal="Fox";Order="Carnivora";Article=(New-UDLink -Text "Wikipedia" -Url "https://en.wikipedia.org/wiki/Fox")} 11 | ) 12 | 13 | $Dashboard = New-UDDashboard -Title "Grids - Custom Columns" -Content { 14 | New-UDGrid -Title "Animals" -Headers @("Animal", "Order", "Wikipedia") -Properties @("Animal", "Order", "Article") -Endpoint { 15 | $Data | Out-UDGridData 16 | } 17 | } 18 | 19 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/charts/tooltips.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of configuring a chart's tooltips. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Data = @( 8 | @{Animal="Frog";Count=10} 9 | @{Animal="Tiger";Count=1} 10 | @{Animal="Bat";Count=34} 11 | @{Animal="Fox";Count=20} 12 | ) 13 | 14 | $Dashboard = New-UDDashboard -Title "Charts - Tooltips" -Content { 15 | 16 | $LegendOptions = New-UDChartTooltipOptions -BackgroundColor "#2928FF" -BodyFontColor "#FFFFFF" -TitleFontColor "#FFFFFF" -CornerRadius 20 -TitleFontSize 50 17 | $Options = New-UDLineChartOptions -TooltipOptions $LegendOptions 18 | 19 | New-UDChart -Title "Big tooltips with custom colors" -Type "Line" -Endpoint { 20 | $Data | Out-UDChartData -LabelProperty "Animal" -Dataset @( 21 | New-UDLineChartDataset -Label "Animals" -DataProperty Count -BackgroundColor "#205D4CFF" -BorderColor "#5D4CFF" -BorderWidth 3 22 | ) 23 | } -Options $Options 24 | } 25 | 26 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/charts/colors.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of working with colors in charts. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Data = @( 8 | @{Animal="Frog";Count=10} 9 | @{Animal="Tiger";Count=1} 10 | @{Animal="Bat";Count=34} 11 | @{Animal="Fox";Count=20} 12 | ) 13 | 14 | $Dashboard = New-UDDashboard -Title "Charts - Colors" -Content { 15 | New-UDChart -Title "Line Chart" -Type "Line" -Endpoint { 16 | $Data | Out-UDChartData -LabelProperty "Animal" -Dataset @( 17 | New-UDLineChartDataset -Label "Animals" -DataProperty Count -BackgroundColor "#234FE2" -BorderColor "#35FF8A" -BorderWidth 3 18 | ) 19 | } 20 | 21 | New-UDChart -Title "Line Chart without Underfill" -Type "Line" -Endpoint { 22 | $Data | Out-UDChartData -LabelProperty "Animal" -Dataset @( 23 | New-UDLineChartDataset -Label "Animals" -DataProperty Count -BorderColor "#35FF8A" -BorderWidth 3 -Fill $false 24 | ) 25 | } 26 | } 27 | 28 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /src/Components/drawer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Drawer from '@mui/material/Drawer' 3 | import { withComponentFeatures } from 'universal-dashboard' 4 | import makeStyles from '@mui/styles/makeStyles'; 5 | var classNames = require('classnames'); 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | button: { 9 | margin: theme.spacing(), 10 | }, 11 | leftIcon: { 12 | marginRight: theme.spacing(), 13 | }, 14 | rightIcon: { 15 | marginLeft: theme.spacing(), 16 | }, 17 | list: { 18 | width: 250, 19 | }, 20 | })) 21 | 22 | const UDDrawer = props => { 23 | const classes = useStyles() 24 | 25 | const onClose = () => { 26 | props.setState({ open: false }) 27 | } 28 | 29 | return ( 30 | 31 |
32 | {props.render(props.children)} 33 |
34 |
35 | ) 36 | } 37 | 38 | export default withComponentFeatures(UDDrawer) 39 | -------------------------------------------------------------------------------- /src/Scripts/interactive/Get-Element.ps1: -------------------------------------------------------------------------------- 1 | function Get-UDElement { 2 | <# 3 | .SYNOPSIS 4 | Get the state of the specified element. 5 | 6 | .DESCRIPTION 7 | Get the state of the specified element. This cmdlet may behave differently depending on the type of parent element. 8 | 9 | .PARAMETER Id 10 | The ID of the element to retreive the state of. 11 | 12 | .EXAMPLE 13 | New-UDCodeEditor -Id 'editor' -Code 'Hello World' 14 | 15 | New-UDButton -Text 'Click Me' -OnClick { 16 | Show-UDToast (Get-UDElement).Code 17 | } 18 | #> 19 | [CmdletBinding()] 20 | param( 21 | [Parameter(Mandatory)] 22 | [string]$Id 23 | ) 24 | 25 | $requestId = '' 26 | 27 | $requestId = [Guid]::NewGuid().ToString() 28 | 29 | $Data = @{ 30 | requestId = $requestId 31 | componentId = $Id 32 | } 33 | 34 | $DashboardHub.SendWebSocketMessage($ConnectionId, "requestState", $Data) 35 | ConvertFrom-Json -InputObject ($stateRequestService.Get($requestId)) 36 | } 37 | -------------------------------------------------------------------------------- /src/Components/progress.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CircularProgress from '@mui/material/CircularProgress'; 3 | import LinearProgress from '@mui/material/LinearProgress'; 4 | import makeStyles from '@mui/styles/makeStyles'; 5 | 6 | const useStyles = makeStyles((theme) => ({ 7 | bar: props => ({ 8 | backgroundColor: props.progressColor ?? null 9 | }), 10 | root: props => ({ 11 | backgroundColor: props.backgroundColor ?? null 12 | }), 13 | circular: props => ({ 14 | color: props.progressColor ?? null 15 | }) 16 | })) 17 | 18 | export default function Progress(props) { 19 | 20 | const classes = useStyles(props); 21 | 22 | if (props.circular) { 23 | return 26 | } 27 | 28 | return
32 | } -------------------------------------------------------------------------------- /src/Components/paper.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import makeStyles from '@mui/styles/makeStyles'; 3 | import Paper from "@mui/material/Paper"; 4 | import classNames from "classnames" 5 | import { withComponentFeatures } from 'universal-dashboard'; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | root: { 9 | flexGrow: 1, 10 | display: "flex", 11 | padding: theme.spacing(2), 12 | margin: theme.spacing() 13 | } 14 | })); 15 | 16 | const UdPaper = (props) => { 17 | 18 | const classes = useStyles(); 19 | 20 | const { 21 | elevation, 22 | style, 23 | height, 24 | width, 25 | square 26 | } = props; 27 | 28 | return ( 29 | 38 | {props.render(props.children)} 39 | 40 | ); 41 | } 42 | 43 | export default withComponentFeatures(UdPaper); 44 | -------------------------------------------------------------------------------- /src/Scripts/interactive/Clear-Element.ps1: -------------------------------------------------------------------------------- 1 | function Clear-UDElement { 2 | <# 3 | .SYNOPSIS 4 | Removes all children from the specified element. 5 | 6 | .DESCRIPTION 7 | Removes all children from the specified element. This cmdlet may behave differently depending on the type of parent element. 8 | 9 | .PARAMETER Id 10 | The ID of the element to clear. 11 | 12 | .PARAMETER Broadcast 13 | Whether to clear the element on all connected clients. 14 | 15 | .EXAMPLE 16 | New-UDElement -Tag 'div' -Id 'parent' -Content { 17 | New-UDTypography -Text 'Hello World' 18 | } 19 | 20 | New-UDButton -Text 'Click Me' -OnClick { 21 | Clear-UDElement -Id 'parent' 22 | } 23 | #> 24 | param( 25 | [Parameter(Mandatory)] 26 | [string]$Id, 27 | [Parameter()] 28 | [Switch]$Broadcast 29 | ) 30 | 31 | if ($Broadcast) { 32 | $DashboardHub.SendWebSocketMessage("clearElement", $Id) 33 | } 34 | else { 35 | $DashboardHub.SendWebSocketMessage($ConnectionId, "clearElement", $Id) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Components/icon-button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import IconButton from '@mui/material/IconButton'; 4 | import classNames from "classnames" 5 | import { withComponentFeatures } from 'universal-dashboard' 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import UdMuIcon from './icon' 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | button: { 11 | margin: theme.spacing(), 12 | }, 13 | })) 14 | 15 | const UdIconButton = props => { 16 | const { className, disabled, id, style, href, icon, onClick } = props; 17 | const classes = useStyles(); 18 | 19 | const handleClick = () => { 20 | if (onClick == null) return 21 | onClick(); 22 | } 23 | 24 | return ( 25 | 33 | 34 | 35 | ); 36 | } 37 | 38 | export default withComponentFeatures(UdIconButton); -------------------------------------------------------------------------------- /src/Scripts/interactive/Set-Clipboard.ps1: -------------------------------------------------------------------------------- 1 | function Set-UDClipboard { 2 | <# 3 | .SYNOPSIS 4 | Sets string data into the clipboard. 5 | 6 | .DESCRIPTION 7 | Sets string data into the clipboard. 8 | 9 | .PARAMETER Data 10 | The data to set into the clipboard. 11 | 12 | .PARAMETER ToastOnSuccess 13 | Show a toast if the clipboard data was sent successfully. 14 | 15 | .PARAMETER ToastOnError 16 | Show a toast if the clipboard data was not sent successfully. 17 | 18 | .EXAMPLE 19 | New-UDButton -Text 'Click Me' -OnClick { 20 | Set-UDClipboard -Data 'Hello World!' -ShowToastOnSuccess 21 | } 22 | #> 23 | param( 24 | [Parameter(Mandatory)] 25 | [string]$Data, 26 | [Parameter()] 27 | [Switch]$ToastOnSuccess, 28 | [Parameter()] 29 | [Switch]$ToastOnError 30 | ) 31 | 32 | $cpData = @{ 33 | data = $Data 34 | toastOnSuccess = $ToastOnSuccess.IsPresent 35 | toastOnError = $ToastOnError.IsPresent 36 | } 37 | 38 | $DashboardHub.SendWebSocketMessage($ConnectionId, "clipboard", $cpData) 39 | } 40 | -------------------------------------------------------------------------------- /src/Scripts/hidden.ps1: -------------------------------------------------------------------------------- 1 | function New-UDHidden { 2 | param( 3 | [Parameter()] 4 | [string]$Id = [Guid]::NewGuid(), 5 | [Parameter(ParameterSetName = 'Down')] 6 | [ValidateSet('xs', 'sm', 'md', 'lg', 'xl')] 7 | [string]$Down, 8 | [Parameter(ParameterSetName = 'Up')] 9 | [ValidateSet('xs', 'sm', 'md', 'lg', 'xl')] 10 | [string]$Up, 11 | [Parameter(ParameterSetName = 'Only')] 12 | [ValidateSet('xs', 'sm', 'md', 'lg', 'xl')] 13 | [string[]]$Only, 14 | [Parameter()] 15 | [ScriptBlock]$Content 16 | ) 17 | 18 | $Component = @{ 19 | type = 'mu-hidden' 20 | id = $Id 21 | isPlugin = $true 22 | 23 | children = & $Content 24 | } 25 | 26 | if ($PSCmdlet.ParameterSetName -eq 'Only') 27 | { 28 | $Component['only'] = $Only 29 | } 30 | 31 | if ($PSCmdlet.ParameterSetName -eq 'Down') 32 | { 33 | $Component["$($Down.ToLower())Down"] = $true 34 | } 35 | 36 | if ($PSCmdlet.ParameterSetName -eq 'Up') 37 | { 38 | $Component["$($Up.ToLower())Up"] = $true 39 | } 40 | 41 | $Component 42 | } -------------------------------------------------------------------------------- /src/Scripts/interactive/Start-Download.ps1: -------------------------------------------------------------------------------- 1 | function Start-UDDownload { 2 | <# 3 | .SYNOPSIS 4 | Starts the download of a file within the dashboard. 5 | 6 | .DESCRIPTION 7 | Starts the download of a file within the dashboard. Only text files are supported 8 | 9 | .PARAMETER FileName 10 | The name of the file. 11 | 12 | .PARAMETER StringData 13 | The data to be written to the file. 14 | 15 | .PARAMETER ContentType 16 | The content type of the file. 17 | 18 | .EXAMPLE 19 | New-UDButton -Text 'Click Me' -OnClick { 20 | Start-UDDownload -FileName 'myfile.txt' -StringData 'Hello World' -ContentType 'text/plain' 21 | } 22 | #> 23 | param( 24 | [Parameter()] 25 | [string]$FileName = "text.txt", 26 | [Parameter(Mandatory)] 27 | [string]$StringData, 28 | [Parameter()] 29 | [string]$ContentType = "text/plain" 30 | ) 31 | 32 | $Data = @{ 33 | fileName = $FileName 34 | stringData = $StringData 35 | contentType = $ContentType 36 | } 37 | 38 | $DashboardHub.SendWebSocketMessage($ConnectionId, "download", $data) 39 | } -------------------------------------------------------------------------------- /src/Components/slider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import {withComponentFeatures} from 'universal-dashboard'; 3 | import Slider from '@mui/material/Slider'; 4 | import {FormContext} from './form'; 5 | 6 | const UDSliderWithContext = (props) => { 7 | return ( 8 | 9 | { 10 | ({onFieldChange}) => 11 | } 12 | 13 | ) 14 | } 15 | 16 | const UDSlider = (props) => { 17 | const onChange = (e, value) => { 18 | props.setState({ value }) 19 | props.onFieldChange({id: props.id, value }); 20 | } 21 | 22 | const onChangeCommitted = (e, value) => { 23 | if (props.onChange) 24 | { 25 | props.onChange(value); 26 | } 27 | } 28 | 29 | useEffect(() => { 30 | props.onFieldChange({id: props.id, value: props.value }); 31 | return () => {} 32 | }, true) 33 | 34 | return 35 | } 36 | 37 | export default withComponentFeatures(UDSliderWithContext); -------------------------------------------------------------------------------- /examples/published-folders/dashboard.ps1: -------------------------------------------------------------------------------- 1 | Import-Module "F:\universal-dashboard-enterprise\src\output\UniversalDashboard.psd1" 2 | 3 | Get-UDDashboard | Stop-UDDashboard 4 | 5 | $Root = $PSScriptRoot 6 | $EndpointInit = New-UDEndpointInitialization -Variable Root 7 | 8 | $Folder = Publish-UDFolder -Path (Join-Path $PSScriptRoot "files") -RequestPath "/files" 9 | 10 | $Dashboard = New-UDDashboard -Title "Downloads" -Content { 11 | New-UDRow -Columns { 12 | New-UDColumn { 13 | New-UDTable -Title "Downloads" -Headers @("Name", "Size", "Download") -Endpoint { 14 | Get-ChildItem -Path (Join-Path $Root "files") | ForEach-Object { 15 | [PSCustomObject]@{ 16 | Name = $_.Name 17 | Size = "$([Math]::Floor($_.Length / 1KB))KB" 18 | Download = New-UDLink -Text "Download" -Url "/files/$($_.Name)" 19 | } 20 | } | Out-UDTableData -Property @("Name", "Size", "Download") 21 | } 22 | } 23 | } 24 | } -EndpointInitialization $EndpointInit 25 | 26 | Start-UDDashboard -Dashboard $Dashboard -Port 10001 -PublishedFolder $Folder -------------------------------------------------------------------------------- /src/classes/classes.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28917.181 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "classes", "classes.csproj", "{E95B1845-A139-4038-939B-060E0FFE2064}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {E95B1845-A139-4038-939B-060E0FFE2064}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {E95B1845-A139-4038-939B-060E0FFE2064}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {E95B1845-A139-4038-939B-060E0FFE2064}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {E95B1845-A139-4038-939B-060E0FFE2064}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {7511C456-2988-4E67-9B34-C220B9DFFF36} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Scripts/interactive/Sync-Element.ps1: -------------------------------------------------------------------------------- 1 | function Sync-UDElement { 2 | <# 3 | .SYNOPSIS 4 | Causes an element to update. 5 | 6 | .DESCRIPTION 7 | Causes an element to update. Not all elements can be updated. For elements that cannot be updated, wrap them in New-UDDynamic and update that. 8 | 9 | .PARAMETER Id 10 | The ID of the element to update. 11 | 12 | .PARAMETER Broadcast 13 | Whether to broadcast the update to all clients. 14 | 15 | .EXAMPLE 16 | New-UDDyanmic -Id 'dateTime' -Content { 17 | Get-Date 18 | } 19 | 20 | New-UDButton -Text 'Refresh' -Content { 21 | Sync-UDElement 'dateTime' 22 | } 23 | #> 24 | param( 25 | [Parameter(Mandatory, ValueFromPipeline)] 26 | [string[]]$Id, 27 | [Parameter()] 28 | [Switch]$Broadcast 29 | ) 30 | 31 | Process { 32 | foreach ($i in $Id) { 33 | if ($Broadcast) { 34 | $DashboardHub.SendWebSocketMessage("syncElement", $I) 35 | } 36 | else { 37 | $DashboardHub.SendWebSocketMessage($ConnectionId, "syncElement", $I) 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | public/ 61 | output/ 62 | 63 | classes/bin 64 | classes/obj 65 | 66 | *.bom.xml -------------------------------------------------------------------------------- /src/Scripts/splitpane.ps1: -------------------------------------------------------------------------------- 1 | function New-UDSplitPane { 2 | param( 3 | [Parameter()] 4 | [string]$Id = ([Guid]::NewGuid()).ToString(), 5 | [Parameter(Mandatory)] 6 | [ScriptBlock]$Content, 7 | [Parameter()] 8 | [ValidateSet("vertical", "horizontal")] 9 | [string]$Direction = "vertical", 10 | [Parameter()] 11 | [int]$MinimumSize, 12 | [Parameter()] 13 | [int]$DefaultSize 14 | ) 15 | 16 | try { 17 | $Children = & $Content 18 | } 19 | catch { 20 | $Children = New-UDError -Message $_ 21 | } 22 | 23 | if ($Children.Length -ne 2) { 24 | Write-Error "Split pane requires exactly two components in Content" 25 | return 26 | } 27 | 28 | $Options = @{ 29 | content = $Children 30 | id = $Id 31 | split = $Direction.ToLower() 32 | type = "ud-splitpane" 33 | } 34 | 35 | if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("MinimumSize")) { 36 | $Options["minSize"] = $MinimumSize 37 | } 38 | 39 | if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("DefaultSize")) { 40 | $Options["defaultSize"] = $DefaultSize 41 | } 42 | 43 | $Options 44 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | PowerShell Universal Dashboard 9 | 34 | 35 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /src/Scripts/interactive/Remove-Element.ps1: -------------------------------------------------------------------------------- 1 | function Remove-UDElement { 2 | <# 3 | .SYNOPSIS 4 | Removes an element from the page. 5 | 6 | .DESCRIPTION 7 | Removes an element from the page. 8 | 9 | .PARAMETER Id 10 | The ID of the element to remove. 11 | 12 | .PARAMETER ParentId 13 | Not used 14 | 15 | .PARAMETER Broadcast 16 | Whether to remove this element from the page of all connected users. 17 | 18 | .EXAMPLE 19 | New-UDElement -Id 'myElement' -Tag 'div' -Content { 20 | New-UDTypography -Text 'Hello World' 21 | } 22 | 23 | New-UDButton -Text 'Click Me' -OnClick { 24 | Remove-UDElement -Id 'myElement' 25 | } 26 | #> 27 | param( 28 | [Parameter(Mandatory)] 29 | [string]$Id, 30 | [Parameter()] 31 | [string]$ParentId, 32 | [Parameter()] 33 | [Switch]$Broadcast 34 | ) 35 | 36 | $Data = @{ 37 | componentId = $Id 38 | parentId = $ParentId 39 | } 40 | 41 | if ($Broadcast) { 42 | $DashboardHub.SendWebSocketMessage("removeElement", $Data) 43 | } 44 | else { 45 | $DashboardHub.SendWebSocketMessage($ConnectionId, "removeElement", $Data) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/templates/Rows and Columns.ps1: -------------------------------------------------------------------------------- 1 | New-UDDashboard -Title 'Rows and Columns' -Content { 2 | New-UDRow -Columns { 3 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 4 | New-UDCard -Text "Row 1, Col 1" 5 | } 6 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 7 | New-UDCard -Text "Row 1, Col 2" 8 | } 9 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 10 | New-UDCard -Text "Row 1, Col 3" 11 | } 12 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 13 | New-UDCard -Text "Row 1, Col 4" 14 | } 15 | } 16 | 17 | New-UDRow -Columns { 18 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 19 | New-UDCard -Text "Row 2, Col 1" 20 | } 21 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 22 | New-UDCard -Text "Row 2, Col 2" 23 | } 24 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 25 | New-UDCard -Text "Row 2, Col 3" 26 | } 27 | New-UDColumn -SmallSize 12 -MediumSize 6 -LargeSize 3 -Content { 28 | New-UDCard -Text "Row 2, Col 4" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Components/table/v2/table.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withComponentFeatures } from "universal-dashboard"; 3 | import { ErrorBoundary } from "react-error-boundary"; 4 | import AlertCard from "./Alert"; 5 | import TableBase from "./baseTable"; 6 | import { generateColumns } from "./utilities"; 7 | 8 | const Table = ({ data, columns, ...restOfProps }) => { 9 | const cols = React.useMemo(() => generateColumns(columns), [restOfProps]); 10 | const isServerSide = restOfProps.loadData ? true : false; 11 | const sortColumn = cols.find(col => col.isDefaultSortColumn)?.accessor || "" 12 | 13 | return ( 14 | 15 | ({ 22 | // style: { 23 | // backgroundColor: cellInfo.value === "Stopped" ? "red" : cellInfo.value === "Running" ? "green" : "unset", 24 | // color: cellInfo.value === "Stopped" ? "#fff" : cellInfo.value === "Running" ? "#fff" : "unset" 25 | // }, 26 | // })} 27 | /> 28 | 29 | ); 30 | }; 31 | 32 | export default withComponentFeatures(Table); -------------------------------------------------------------------------------- /src/Scripts/paragraph.ps1: -------------------------------------------------------------------------------- 1 | function New-UDParagraph { 2 | <# 3 | .SYNOPSIS 4 | A paragraph. 5 | 6 | .DESCRIPTION 7 | A paragraph. Used to define a P HTML tag. 8 | 9 | .PARAMETER Content 10 | The content of the paragraph. 11 | 12 | .PARAMETER Text 13 | The text of the paragraph. 14 | 15 | .PARAMETER Color 16 | The font color of the paragraph. 17 | 18 | .EXAMPLE 19 | A simple paragraph. 20 | 21 | New-UDParagraph -Text 'Hello, world!' 22 | #> 23 | param( 24 | [Parameter(ParameterSetName = 'content')] 25 | [ScriptBlock]$Content, 26 | [Parameter(ParameterSetName = 'text')] 27 | [string]$Text, 28 | [Parameter()] 29 | [UniversalDashboard.Models.DashboardColor]$Color = 'black' 30 | ) 31 | 32 | if ($PSCmdlet.ParameterSetName -eq 'content') { 33 | New-UDElement -Tag 'p' -Content $Content -Attributes @{ 34 | style = @{ 35 | color = $Color.HtmlColor 36 | } 37 | } 38 | } 39 | else { 40 | New-UDElement -Tag 'p' -Content { 41 | $Text 42 | } -Attributes @{ 43 | style = @{ 44 | color = $Color.HtmlColor 45 | } 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/app/services/render-service.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | 3 | const UdHtmlComponent = React.lazy(() => import('./../ud-html.jsx' /* webpackChunkName: "ud-html" */)) 4 | const UdElementComponent = React.lazy(() => import('./../ud-element.jsx' /* webpackChunkName: "ud-element" */)) 5 | const UdIcon = React.lazy(() => import('./../ud-icon.jsx' /* webpackChunkName: "ud-icon" */)) 6 | 7 | export function internalRenderComponent(component, history) { 8 | if (!component) return null; 9 | 10 | switch (component.type) { 11 | 12 | case "icon": 13 | return ; 14 | 15 | case "element": 16 | return 17 | 18 | 19 | 20 | case "rawHtml": 21 | return 22 | 23 | 24 | } 25 | 26 | return component; 27 | } 28 | 29 | export default function renderComponent(component, history, dynamicallyLoaded) { 30 | return window.UniversalDashboard.renderComponent(component, history, dynamicallyLoaded); 31 | } -------------------------------------------------------------------------------- /src/app/ud-tooltip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactTooltip from 'react-tooltip'; 3 | 4 | export default class UDTooltip extends React.Component { 5 | render() 6 | { 7 | var tooltipContent = this.props.tooltipContent; 8 | if (!Array.isArray(tooltipContent)) 9 | { 10 | tooltipContent = [tooltipContent] 11 | } 12 | 13 | var content = this.props.content; 14 | if (!Array.isArray(content)) 15 | { 16 | content = [content] 17 | } 18 | 19 | content = content.map(x => { 20 | return UniversalDashboard.renderComponent(x); 21 | }); 22 | 23 | tooltipContent = tooltipContent.map(x => { 24 | if (typeof x === 'string' || x instanceof String) 25 | { 26 | return x; 27 | } 28 | 29 | return UniversalDashboard.renderComponent(x); 30 | }); 31 | 32 | return ( 33 | 34 | {content} 35 | {tooltipContent} 36 | 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /src/Components/dynamic.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { withComponentFeatures } from 'universal-dashboard'; 3 | import ReactInterval from 'react-interval'; 4 | 5 | const UDDynamic = (props) => { 6 | const [component, setComponent] = useState({}); 7 | const [loading, setLoading] = useState(props.loadingComponent != null); 8 | 9 | const loadData = () => { 10 | setLoading(props.loadingComponent != null); 11 | props.get(props.id).then(x => { 12 | 13 | if (Array.isArray(x)) { 14 | x.forEach(y => y.__version = props.__version); 15 | } 16 | 17 | setComponent(x); 18 | setLoading(false); 19 | }); 20 | } 21 | 22 | const refresh = () => { 23 | props.setState({ __version: Math.random().toString(36).substr(2, 5) }); 24 | } 25 | 26 | useEffect(() => { 27 | loadData(); 28 | 29 | return () => { } 30 | }, [props.__version]) 31 | 32 | if (loading) { 33 | return props.render(props.loadingComponent); 34 | } 35 | 36 | return [ 37 | props.render(component), 38 | 39 | ] 40 | } 41 | 42 | export default withComponentFeatures(UDDynamic); -------------------------------------------------------------------------------- /src/Scripts/map/rasterlayer.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapRasterLayer { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter(ParameterSetName = "Generic")] 6 | [string]$TileServer = 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png', 7 | [Parameter(ParameterSetName = "Bing", Mandatory)] 8 | [string]$ApiKey, 9 | [Parameter(ParameterSetName = "Bing")] 10 | [ValidateSet("Aerial", "AerialWithLabels", "AerialWithLabelsOnDemand", "CanvasDark", "CanvasLight", "CanvasGray", "Road")] 11 | [string]$Type = "Aerial", 12 | [Parameter(ParameterSetName = "Bing", Mandatory)] 13 | [Switch]$Bing, 14 | [Parameter()] 15 | [string]$Attribution, 16 | [Parameter()] 17 | [int]$Opacity, 18 | [Parameter()] 19 | [int]$ZIndex, 20 | [Parameter()] 21 | [string]$Name 22 | ) 23 | 24 | @{ 25 | type = "map-raster-layer" 26 | isPlugin = $true 27 | assetId = $AssetId 28 | 29 | id = $id 30 | tileServer = $TileServer 31 | apiKey = $ApiKey 32 | attribution = $Attribution 33 | opacity = $Opactiy 34 | zIndex = $ZIndex 35 | name = $Name 36 | bing = $Bing.IsPresent 37 | mapType = $Type 38 | } 39 | } -------------------------------------------------------------------------------- /src/Components/framework/ud-link.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class UdLink extends React.Component { 4 | 5 | 6 | onClick(e) { 7 | if (this.props.onClick == null) { 8 | return 9 | } 10 | 11 | e.preventDefault(); 12 | 13 | UniversalDashboard.publish('element-event', { 14 | type: "clientEvent", 15 | eventId: this.props.onClick, 16 | eventName: 'onClick', 17 | eventData: '' 18 | }); 19 | } 20 | 21 | render() { 22 | var target = this.props.openInNewWindow ? "_blank" : "_self"; 23 | 24 | var style = { 25 | color: this.props.color 26 | } 27 | 28 | if (this.props.icon) { 29 | var icon = UniversalDashboard.renderComponent({type: 'icon', icon: this.props.icon}); 30 | return 31 | {icon} {this.props.text} 32 | 33 | } 34 | else { 35 | return {this.props.text} 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Scripts/map/marker.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMapMarker { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter(ParameterSetName = "LatLng", Mandatory)] 6 | [float]$Longitude, 7 | [Parameter(ParameterSetName = "LatLng", Mandatory)] 8 | [float]$Latitude, 9 | [Parameter()] 10 | [string]$Attribution, 11 | [Parameter()] 12 | [int]$Opacity, 13 | [Parameter()] 14 | [int]$ZIndex, 15 | [Parameter()] 16 | [Hashtable]$Popup, 17 | [Parameter()] 18 | [Hashtable]$Icon, 19 | [Parameter(ParameterSetName = "GeoJSON", Mandatory)] 20 | [string]$GeoJSON 21 | ) 22 | 23 | if ($PSCmdlet.ParameterSetName -eq 'GeoJSON') { 24 | $Json = $GeoJSON | ConvertFrom-Json 25 | $Coordinates = $Json.Geometry.Coordinates 26 | $Latitude = $Coordinates[1] 27 | $Longitude = $Coordinates[0] 28 | } 29 | 30 | @{ 31 | type = "map-marker" 32 | isPlugin = $true 33 | assetId = $AssetId 34 | 35 | id = $id 36 | longitude = $Longitude 37 | latitude = $Latitude 38 | attribution = $Attribution 39 | opacity = $Opacity 40 | zIndex = $ZIndex 41 | popup = $Popup 42 | icon = $Icon 43 | } 44 | } -------------------------------------------------------------------------------- /examples/ad-onboarding-dashboard.ps1: -------------------------------------------------------------------------------- 1 | Import-Module UniversalDashboard 2 | Add-Type -AssemblyName 'System.Web' 3 | $Dashboard = New-UDDashboard -Title "Create new user" -Content { 4 | New-UDInput -Title "Create new user" -Endpoint { 5 | param( 6 | [Parameter(Mandatory)] 7 | [string]$FirstName, 8 | [Parameter(Mandatory)] 9 | [string]$LastName, 10 | [Parameter(Mandatory)] 11 | [string]$UserName, 12 | [Parameter(Mandatory)] 13 | [ValidateSet("IT", "HR", "Accounting", "Development")] 14 | [string]$Group 15 | ) 16 | 17 | $password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3) 18 | $securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force 19 | 20 | $NewAdUserParameters = @{ 21 | GivenName = $FirstName 22 | Surname = $LastName 23 | Name = $UserName 24 | AccountPassword = $securePassword 25 | } 26 | 27 | New-AdUser @NewAdUserParameters 28 | Add-AdGroupMember -Identity $Group -Members $userName 29 | 30 | New-UDInputAction -Content { 31 | New-UDCard -Title "Temporary Password" -Text $Password 32 | } 33 | } -Validate 34 | } 35 | 36 | Start-UDDashboard -Dashboard $Dashboard -Port 10000 -------------------------------------------------------------------------------- /src/Components/charts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const NivoBar = React.lazy(() => import('./nivo-bar')); 3 | const NivoCalendar = React.lazy(() => import('./nivo-calendar')); 4 | const NivoHeatmap = React.lazy(() => import('./nivo-heatmap')); 5 | const NivoLine = React.lazy(() => import('./nivo-line')); 6 | const NivoPie = React.lazy(() => import('./nivo-pie')); 7 | const NivoStream = React.lazy(() => import('./nivo-stream')); 8 | const NivoTreemap = React.lazy(() => import('./nivo-treemap')); 9 | const NivoBubble = React.lazy(() => import('./nivo-bubble')); 10 | const UDSparklines = React.lazy(() => import('./sparklines')); 11 | const UDChart = React.lazy(() => import('./ud-chart')); 12 | const UdMonitor = React.lazy(() => import('./monitor')); 13 | 14 | UniversalDashboard.register("nivo-bar", NivoBar); 15 | UniversalDashboard.register("nivo-calendar", NivoCalendar); 16 | UniversalDashboard.register("nivo-heatmap", NivoHeatmap); 17 | UniversalDashboard.register("nivo-line", NivoLine); 18 | UniversalDashboard.register("nivo-pie", NivoPie); 19 | UniversalDashboard.register("nivo-stream", NivoStream); 20 | UniversalDashboard.register("nivo-treemap", NivoTreemap); 21 | UniversalDashboard.register("nivo-bubble", NivoBubble); 22 | UniversalDashboard.register("sparklines", UDSparklines); 23 | UniversalDashboard.register("ud-chartjs", UDChart); 24 | UniversalDashboard.register("chartjs-monitor", UdMonitor); -------------------------------------------------------------------------------- /src/Components/charts/sparklines.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Sparklines, SparklinesLine, SparklinesBars } from 'react-sparklines'; 3 | 4 | export default class UDSparklines extends React.Component { 5 | render() { 6 | 7 | var { data, limit, width, height, margin, color, sparkType, min, max } = this.props; 8 | 9 | if (sparkType === "lines") { 10 | return ( 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | if (sparkType === "bars") { 18 | return ( 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | if (sparkType === "both") { 26 | return ( 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Scripts/container.ps1: -------------------------------------------------------------------------------- 1 | function New-UDContainer { 2 | <# 3 | .SYNOPSIS 4 | Creates a Material UI container. 5 | 6 | .DESCRIPTION 7 | Creates a Material UI container. Containers pad the left and right side of the contained content to center it on larger resolution screens. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Children 13 | The child items to include within the container. 14 | 15 | .EXAMPLE 16 | Creates a container with some text. 17 | 18 | New-UDContainer -Content { 19 | New-UDTypography -Text 'Nice' 20 | } 21 | #> 22 | param( 23 | [Parameter()] 24 | [string]$Id = ([Guid]::NewGuid()).ToString(), 25 | 26 | [Alias("Content")] 27 | [Parameter(Mandatory, Position = 0)] 28 | [ScriptBlock]$Children, 29 | 30 | [Parameter()] 31 | [string]$ClassName 32 | ) 33 | 34 | Process { 35 | try { 36 | $c = New-UDErrorBoundary -Content $Children 37 | } 38 | catch { 39 | $c = New-UDError -Message $_ 40 | } 41 | 42 | 43 | @{ 44 | isPlugin = $true 45 | id = $id 46 | assetId = $MUAssetId 47 | type = "mu-container" 48 | 49 | children = $c 50 | className = $ClassName 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/app/ud-icon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from "classnames" 3 | 4 | import { library } from '@fortawesome/fontawesome-svg-core' 5 | import { fab } from '@fortawesome/free-brands-svg-icons' 6 | import { fas } from '@fortawesome/free-solid-svg-icons' 7 | import { far } from '@fortawesome/free-regular-svg-icons' 8 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 9 | 10 | import brands from './fontawesome.brands' 11 | import regular from './fontawesome.regular' 12 | import solid from './fontawesome.solid' 13 | 14 | library.add(far, fab, fas) 15 | 16 | const UDIcon = (props) => { 17 | 18 | var lookup = Array.isArray(props.icon) ? props.icon[0] : props.icon; 19 | var iconName = Array.isArray(props.icon) ? props.icon[0] : props.icon; 20 | iconName = iconName.match(/[A-Z]+(?![a-z])|[A-Z]?[a-z]+|\d+/g).join('-').toLowerCase(); 21 | 22 | var icon; 23 | if (regular.find(x => x === lookup)) { 24 | icon = ["far", iconName] 25 | } 26 | else if (solid.find(x => x === lookup)) { 27 | icon = ["fas", iconName] 28 | } 29 | else if (brands.find(x => x === lookup)) { 30 | icon = ["fab", iconName] 31 | } else { 32 | icon = ["far", 'questionCircle'] 33 | } 34 | 35 | return ( 36 | 37 | ) 38 | } 39 | export default UDIcon; -------------------------------------------------------------------------------- /src/Scripts/interactive/Add-Element.ps1: -------------------------------------------------------------------------------- 1 | function Add-UDElement { 2 | <# 3 | .SYNOPSIS 4 | Adds an element to a parent element. 5 | 6 | .DESCRIPTION 7 | Adds an element to a parent element. This cmdlet may behave differently depending on the type of parent element. 8 | 9 | .PARAMETER ParentId 10 | The parent element ID to add the item to. 11 | 12 | .PARAMETER Content 13 | The content to add to the parent element. 14 | 15 | .PARAMETER Broadcast 16 | Whether to update all connected dashboards (all users). 17 | 18 | .EXAMPLE 19 | New-UDElement -Tag 'div' -Id 'parent' -Content { 20 | 21 | } 22 | 23 | New-UDButton -Text 'Click Me' -OnClick { 24 | Add-UDElement -ParentId 'parent' -Content { 25 | New-UDTypography -Text 'Hello World' 26 | } 27 | } 28 | #> 29 | param( 30 | [Parameter(Mandatory)] 31 | [string]$ParentId, 32 | [Parameter(Mandatory)] 33 | [ScriptBlock]$Content, 34 | [Parameter()] 35 | [Switch]$Broadcast 36 | ) 37 | 38 | $NewContent = & $Content 39 | 40 | $Data = @{ 41 | componentId = $ParentId 42 | elements = $NewContent 43 | } 44 | 45 | if ($Broadcast) { 46 | $DashboardHub.SendWebSocketMessage("addElement", $Data) 47 | } 48 | else { 49 | $DashboardHub.SendWebSocketMessage($ConnectionId, "addElement", $Data) 50 | } 51 | } -------------------------------------------------------------------------------- /src/Scripts/alert.ps1: -------------------------------------------------------------------------------- 1 | function New-UDAlert { 2 | <# 3 | .SYNOPSIS 4 | Creates an alert. 5 | 6 | .DESCRIPTION 7 | Creates an alert. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Severity 13 | The severity of this alert. 14 | 15 | .PARAMETER Children 16 | Content of the alert. 17 | 18 | .PARAMETER Text 19 | Text for the body of the alert. 20 | 21 | .PARAMETER Title 22 | A title for this alert. 23 | #> 24 | param( 25 | [Parameter()] 26 | [string]$Id = [Guid]::NewGuid(), 27 | [Parameter()] 28 | [ValidateSet("success", "error", "warning", "info")] 29 | [string]$Severity = "success", 30 | [Parameter(ParameterSetName = "Content")] 31 | [Alias("Content")] 32 | [scriptblock]$Children, 33 | [Parameter(ParameterSetName = "Text")] 34 | [string]$Text, 35 | [Parameter()] 36 | [string]$Title, 37 | [Parameter()] 38 | [string]$ClassName 39 | ) 40 | 41 | if ($PSCmdlet.ParameterSetName -eq 'Text') { 42 | $Children = { $Text } 43 | } 44 | 45 | @{ 46 | type = "mu-alert" 47 | id = $id 48 | isPlugin = $true 49 | assetId = $MUAssetId 50 | 51 | severity = $Severity.ToLower() 52 | children = & $Children 53 | title = $Title 54 | className = $ClassName 55 | } 56 | } -------------------------------------------------------------------------------- /examples/formatting/rows-and-columns.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of using rows and columns. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Formatting - Rows and Columns" -Content { 8 | New-UDRow -Columns { 9 | New-UDColumn -Size 6 -Content { 10 | New-UDCard -Title "Row 1 - Column 1 - Size 6" 11 | } 12 | New-UDColumn -Size 6 -Content { 13 | New-UDCard -Title "Row 1 - Column 2 - Size 6" 14 | } 15 | } 16 | New-UDRow -Columns { 17 | New-UDColumn -Size 3 -Content { 18 | New-UDCard -Title "Row 2 - Column 1 - Size 3" 19 | } 20 | New-UDColumn -Size 3 -Content { 21 | New-UDCard -Title "Row 2 - Column 2 - Size 3" 22 | } 23 | New-UDColumn -Size 3 -Content { 24 | New-UDCard -Title "Row 2 - Column 3 - Size 3" 25 | } 26 | New-UDColumn -Size 3 -Content { 27 | New-UDCard -Title "Row 2 - Column 4 - Size 3" 28 | } 29 | } 30 | New-UDRow -Columns { 31 | New-UDColumn -Size 6 -Content { 32 | New-UDCard -Title "Row 3 - Column 1 - Size 6" 33 | } 34 | New-UDColumn -Size 3 -Content { 35 | New-UDCard -Title "Row 3 - Column 2 - Size 3" 36 | } 37 | New-UDColumn -Size 3 -Content { 38 | New-UDCard -Title "Row 3 - Column 3 - Size 3" 39 | } 40 | } 41 | } 42 | 43 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 -------------------------------------------------------------------------------- /examples/grid/high-performance-sql-grid.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Provides an example of overriding the default filtering, sort and paging mechanism by querying SQL. 3 | #> 4 | 5 | Import-Module UniversalDashboard 6 | 7 | $Dashboard = New-UDDashboard -Title "Grids - Simple" -Content { 8 | New-UDGrid -Title "Animals" -Headers @("Animal", "Order") -Properties @("Animal", "Order") -Endpoint { 9 | #Overrides filtering, sorting and paging. This ensures that SQL does the actions. 10 | #All UDGrid endpioints have the following variables: 11 | # - $sortAscending - Whether or not the sort is ascending 12 | # - $sortColumn - The name of the column to sort on 13 | # - $filterText - The text to filter the data by 14 | # - $skip - The number of items to skip (used in paging) 15 | # - $take - The number of items to return (used in paging) 16 | 17 | $sortOrder = "DESC" 18 | if ($sortAscending ) { 19 | $sortOrder = "ASC" 20 | } 21 | 22 | $TotalItems = (Invoke-SqlCmd -Query "SELECT COUNT(*) as Total FROM animals WHERE Animal LIKE '%$filterText%' ").Total 23 | 24 | $Result = Invoke-SqlCmd -Query "SELECT Animal, Order FROM animals WHERE Animal LIKE '%$filterText%' ORDER BY $sortColumn $sortOrder OFFSET ($skip) ROWS FETCH NEXT ($take) ROWS ONLY " 25 | 26 | $Result | Out-UDGridData -TotalItems $TotalItems 27 | } -ServerSideProcessing 28 | } 29 | 30 | Start-UDDashboard -Dashboard $Dashboard -Port 8080 31 | -------------------------------------------------------------------------------- /src/Components/map/index.js: -------------------------------------------------------------------------------- 1 | require('leaflet/dist/leaflet.css') 2 | require('leaflet/dist/leaflet.js') 3 | import React from 'react'; 4 | 5 | const UDMap = React.lazy(() => import('./map')); 6 | const UDLayerControl = React.lazy(() => import('./UDLayerControl')); 7 | const UDLayerControlBaseLayer = React.lazy(() => import('./UDLayerControlBaseLayer')); 8 | const UDLayerControlOverlay = React.lazy(() => import('./UDLayerControlOverlay')); 9 | const UDMarker = React.lazy(() => import('./marker')); 10 | const UDVectorLayer = React.lazy(() => import('./vector-layer')); 11 | const UDRasterLayer = React.lazy(() => import('./raster-layer')); 12 | const UDFeatureGroup = React.lazy(() => import('./feature-group')); 13 | const UDHeatmapLayer = React.lazy(() => import('./heatmap-layer')); 14 | const UDClusterLayer = React.lazy(() => import('./cluster-layer')); 15 | 16 | UniversalDashboard.register("ud-map", UDMap); 17 | UniversalDashboard.register("map-layer-control", UDLayerControl); 18 | UniversalDashboard.register("map-base-layer", UDLayerControlBaseLayer); 19 | UniversalDashboard.register("map-overlay", UDLayerControlOverlay); 20 | UniversalDashboard.register("map-marker", UDMarker); 21 | UniversalDashboard.register("map-vector-layer", UDVectorLayer); 22 | UniversalDashboard.register("map-raster-layer", UDRasterLayer); 23 | UniversalDashboard.register("map-feature-group", UDFeatureGroup); 24 | UniversalDashboard.register("map-heatmap-layer", UDHeatmapLayer); 25 | UniversalDashboard.register("map-cluster-layer", UDClusterLayer); -------------------------------------------------------------------------------- /src/Scripts/backdrop.ps1: -------------------------------------------------------------------------------- 1 | function New-UDBackdrop { 2 | <# 3 | .SYNOPSIS 4 | Creates an overlay over the current page. 5 | 6 | .DESCRIPTION 7 | Creates an overlay over the current page. 8 | 9 | .PARAMETER Id 10 | The ID of this component 11 | 12 | .PARAMETER Color 13 | The color of the backdrop. 14 | 15 | .PARAMETER Children 16 | Child components to include in the backdrop. 17 | 18 | .PARAMETER Open 19 | Whether the backdrop is open. 20 | 21 | .PARAMETER OnClick 22 | A script block to invoke when the backdrop is clicked. 23 | #> 24 | param( 25 | [Parameter ()] 26 | [string]$Id = ([Guid]::NewGuid()).ToString(), 27 | [Parameter()] 28 | [DashboardColor]$Color = '#fff', 29 | [Parameter(Mandatory)] 30 | [Alias("Content")] 31 | [ScriptBlock]$Children, 32 | [Parameter()] 33 | [Switch]$Open, 34 | [Parameter()] 35 | [Endpoint]$OnClick, 36 | [Parameter()] 37 | [string]$ClassName 38 | ) 39 | 40 | if ($OnClick) { 41 | $OnClick.Register($Id, $PSCmdlet) 42 | } 43 | 44 | @{ 45 | type = 'mu-backdrop' 46 | isPlugin = $true 47 | assetId = $MUAssetId 48 | 49 | id = $Id 50 | color = $Color.HtmlColor 51 | children = & $Children 52 | open = $Open.IsPresent 53 | onClick = $OnClick 54 | className = $ClassName 55 | } 56 | } -------------------------------------------------------------------------------- /src/Scripts/heading.ps1: -------------------------------------------------------------------------------- 1 | function New-UDHeading { 2 | <# 3 | .SYNOPSIS 4 | Defines a new heading 5 | 6 | .DESCRIPTION 7 | Defines a new heading. This generates a new H tag. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Content 13 | The content of the heading. 14 | 15 | .PARAMETER Text 16 | The text of the heading. 17 | 18 | .PARAMETER Size 19 | This size of this heading. This can be 1,2,3,4,5 or 6. 20 | 21 | .PARAMETER Color 22 | The text color. 23 | 24 | .EXAMPLE 25 | A heading 26 | 27 | New-UDHeading -Text 'Heading 1' -Size 1 -Color Blue 28 | #> 29 | param( 30 | [Parameter()] 31 | [String]$Id = ([Guid]::NewGuid()), 32 | [Parameter(ParameterSetName = "Content")] 33 | [ScriptBlock]$Content, 34 | [Parameter(ParameterSetName = "Text")] 35 | [string]$Text, 36 | [Parameter()] 37 | [ValidateSet("1", "2", "3", "4", "5", "6")] 38 | $Size, 39 | [Parameter()] 40 | [UniversalDashboard.Models.DashboardColor]$Color = 'black', 41 | [Parameter()] 42 | [string]$ClassName 43 | ) 44 | 45 | if ($PSCmdlet.ParameterSetName -eq "Text") { 46 | $Content = { $Text } 47 | } 48 | 49 | New-UDElement -Id $Id -Tag "h$size" -Content $Content -Attributes @{ 50 | className = $className 51 | style = @{ 52 | color = $Color.HtmlColor 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Scripts/skeleton.ps1: -------------------------------------------------------------------------------- 1 | function New-UDSkeleton { 2 | <# 3 | .SYNOPSIS 4 | Creates a loading skeleton 5 | 6 | .DESCRIPTION 7 | Creates a loading skeleton 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Variant 13 | The type of skeleton to display. Valid values are "text", "rect", "circle" 14 | 15 | .PARAMETER Height 16 | The static height of the skeleton. 17 | 18 | .PARAMETER Width 19 | The static width of the skeleton. 20 | 21 | .PARAMETER Animation 22 | The type of animation to use for the skeleton. Valid values are: "wave", "disabled", "pulsate" 23 | #> 24 | param( 25 | [Parameter()] 26 | [String]$Id = ([Guid]::NewGuid()), 27 | [Parameter()] 28 | [ValidateSet("text", "rect", "circle")] 29 | [string]$Variant = 'text', 30 | [Parameter()] 31 | [int]$Height, 32 | [Parameter()] 33 | [int]$Width, 34 | [Parameter()] 35 | [ValidateSet("wave", "disabled", "pulsate")] 36 | [string]$Animation = "pulsate", 37 | [Parameter()] 38 | [string]$ClassName 39 | ) 40 | 41 | @{ 42 | type = "mu-skeleton" 43 | id = $Id 44 | isPlugin = $true 45 | assetId = $MUAssetId 46 | 47 | variant = $Variant.ToLower() 48 | height = $Height 49 | width = $Width 50 | animation = $Animation.ToLower() 51 | className = $ClassName 52 | } 53 | } -------------------------------------------------------------------------------- /src/Components/radio.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { withComponentFeatures } from 'universal-dashboard'; 3 | import { FormContext } from './form'; 4 | import RadioGroup from '@mui/material/RadioGroup'; 5 | import Radio from '@mui/material/Radio'; 6 | import FormControlLabel from '@mui/material/FormControlLabel'; 7 | 8 | export const UDRadioGroupWithContext = withComponentFeatures((props) => { 9 | return ( 10 | 11 | { 12 | ({ onFieldChange }) => 13 | } 14 | 15 | ) 16 | }) 17 | 18 | const UDRadioGroup = (props) => { 19 | 20 | const onChange = (event) => { 21 | props.onFieldChange({ id: props.id, value: event.target.value }) 22 | props.setState({ value: event.target.value }) 23 | 24 | if (props.onChange) { 25 | props.onChange(event.target.value); 26 | } 27 | } 28 | 29 | useEffect(() => { 30 | props.onFieldChange({ id: props.id, value: props.value }); 31 | return () => { } 32 | }, true) 33 | 34 | return ( 35 | onChange(e)}> 36 | {props.render(props.children)} 37 | 38 | ) 39 | } 40 | 41 | export const UDRadio = withComponentFeatures((props) => { 42 | return ( 43 | }> 44 | 45 | ) 46 | }) -------------------------------------------------------------------------------- /src/Components/transition.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Collapse, Slide, Zoom, Grow, Fade } from '@mui/material'; 3 | import { withComponentFeatures } from 'universal-dashboard'; 4 | 5 | class TransitionWrapper extends React.Component { 6 | render() { 7 | return this.props.children; 8 | } 9 | } 10 | 11 | function Transition(props) { 12 | if (props.transition === "fade") { 13 | return 14 | {props.render(props.children)} 15 | 16 | } 17 | 18 | if (props.transition === "zoom") { 19 | return 20 | {props.render(props.children)} 21 | 22 | } 23 | 24 | if (props.transition === "grow") { 25 | return 26 | {props.render(props.children)} 27 | 28 | } 29 | 30 | if (props.transition === "slide") { 31 | return 32 | {props.render(props.children)} 33 | 34 | } 35 | 36 | if (props.transition === "collapse") { 37 | return ( 38 | 39 | {props.render(props.children)} 40 | 41 | ); 42 | } 43 | } 44 | 45 | export default withComponentFeatures(Transition); -------------------------------------------------------------------------------- /src/Components/map/MarkerCluster.Default.css: -------------------------------------------------------------------------------- 1 | .marker-cluster-small { 2 | background-color: rgba(181, 226, 140, 0.6); 3 | } 4 | .marker-cluster-small div { 5 | background-color: rgba(110, 204, 57, 0.6); 6 | } 7 | 8 | .marker-cluster-medium { 9 | background-color: rgba(241, 211, 87, 0.6); 10 | } 11 | .marker-cluster-medium div { 12 | background-color: rgba(240, 194, 12, 0.6); 13 | } 14 | 15 | .marker-cluster-large { 16 | background-color: rgba(253, 156, 115, 0.6); 17 | } 18 | .marker-cluster-large div { 19 | background-color: rgba(241, 128, 23, 0.6); 20 | } 21 | 22 | /* IE 6-8 fallback colors */ 23 | .leaflet-oldie .marker-cluster-small { 24 | background-color: rgb(181, 226, 140); 25 | } 26 | .leaflet-oldie .marker-cluster-small div { 27 | background-color: rgb(110, 204, 57); 28 | } 29 | 30 | .leaflet-oldie .marker-cluster-medium { 31 | background-color: rgb(241, 211, 87); 32 | } 33 | .leaflet-oldie .marker-cluster-medium div { 34 | background-color: rgb(240, 194, 12); 35 | } 36 | 37 | .leaflet-oldie .marker-cluster-large { 38 | background-color: rgb(253, 156, 115); 39 | } 40 | .leaflet-oldie .marker-cluster-large div { 41 | background-color: rgb(241, 128, 23); 42 | } 43 | 44 | .marker-cluster { 45 | background-clip: padding-box; 46 | border-radius: 20px; 47 | } 48 | .marker-cluster div { 49 | width: 30px; 50 | height: 30px; 51 | margin-left: 5px; 52 | margin-top: 5px; 53 | 54 | text-align: center; 55 | border-radius: 15px; 56 | font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; 57 | } 58 | .marker-cluster span { 59 | line-height: 30px; 60 | } -------------------------------------------------------------------------------- /src/Scripts/interactive/Set-Element.ps1: -------------------------------------------------------------------------------- 1 | function Set-UDElement { 2 | <# 3 | .SYNOPSIS 4 | Set properties of an element. 5 | 6 | .DESCRIPTION 7 | Set the properties of an element. 8 | 9 | .PARAMETER Id 10 | The element to set properites on. 11 | 12 | .PARAMETER Properties 13 | The properties to set in the form of a hashtable. 14 | 15 | .PARAMETER Broadcast 16 | Whether to update this element on all connected clients. 17 | 18 | .PARAMETER Content 19 | Content to set within the element. 20 | 21 | .EXAMPLE 22 | New-UDButton -Id 'button' -Text 'Disable Me' -OnClick { 23 | Set-UDElement -Id 'button -Properties @{ 24 | 'disabled' = $true 25 | } 26 | } 27 | #> 28 | param( 29 | [Parameter(Mandatory)] 30 | [string]$Id, 31 | [Alias("Attributes")] 32 | [Parameter()] 33 | [Hashtable]$Properties, 34 | [Parameter()] 35 | [Switch]$Broadcast, 36 | [Parameter()] 37 | [ScriptBlock]$Content 38 | ) 39 | 40 | if ($Content -and -not $Properties) { 41 | $Properties = @{} 42 | } 43 | 44 | if ($Content) { 45 | $Properties['content'] = [Array](& $Content) 46 | } 47 | 48 | $Data = @{ 49 | componentId = $Id 50 | state = $Properties 51 | } 52 | 53 | if ($Broadcast) { 54 | $DashboardHub.SendWebSocketMessage("setState", $data) 55 | } 56 | else { 57 | $DashboardHub.SendWebSocketMessage($ConnectionId, "setState", $Data) 58 | } 59 | } -------------------------------------------------------------------------------- /src/Components/table/v2/columns.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Checkbox } from '@mui/material' 3 | 4 | const SelectCheckBox = React.forwardRef( 5 | ({ indeterminate, onRowSelection, singleSelection, toggleAllRowsSelected, onChange: initialOnChange, ...rest }, ref) => { 6 | const defaultRef = React.useRef() 7 | const resolvedRef = ref || defaultRef 8 | 9 | React.useEffect(() => { 10 | resolvedRef.current.indeterminate = indeterminate 11 | }, [resolvedRef, indeterminate]) 12 | 13 | function onChange(event, checked) { 14 | if (singleSelection) 15 | { 16 | toggleAllRowsSelected(false); 17 | } 18 | 19 | rest.row.toggleRowSelected() 20 | if (onRowSelection) { 21 | onRowSelection({ id: rest.row.id, ...rest.row.values, selected: !rest.row.isSelected }) 22 | } 23 | } 24 | 25 | return 26 | } 27 | ) 28 | 29 | const SelectAllCheckBox = React.forwardRef( 30 | ({ indeterminate, onRowSelection, onChange, ...rest }, ref) => { 31 | const defaultRef = React.useRef() 32 | const resolvedRef = ref || defaultRef 33 | 34 | React.useEffect(() => { 35 | resolvedRef.current.indeterminate = indeterminate 36 | }, [resolvedRef, indeterminate]) 37 | 38 | function onChangeSelection(event, checked) { 39 | onChange(event, checked); 40 | if (onRowSelection) { 41 | onRowSelection({ id: 'all', selected: checked }) 42 | } 43 | } 44 | 45 | return 46 | } 47 | ) 48 | 49 | export { SelectCheckBox, SelectAllCheckBox } -------------------------------------------------------------------------------- /src/Scripts/avatar.ps1: -------------------------------------------------------------------------------- 1 | function New-UDAvatar { 2 | <# 3 | .SYNOPSIS 4 | Creates a new Avatar. 5 | 6 | .DESCRIPTION 7 | Creates a new Avatar. An avatar is typically an image of a user. 8 | 9 | .PARAMETER Id 10 | The ID of the component. It defaults to a random GUID. 11 | 12 | .PARAMETER Image 13 | The URL of an image to show in the avatar. 14 | 15 | .PARAMETER Alt 16 | The alt text to assign to the avatar. 17 | 18 | .PARAMETER ClassName 19 | Classes to assign to the avatar component. 20 | 21 | .PARAMETER Variant 22 | The variant type of the avatar. 23 | 24 | .EXAMPLE 25 | A small avatar using Alon's image. 26 | 27 | New-UDAvatar -Image 'https://avatars2.githubusercontent.com/u/34351424?s=460&v=4' -Alt 'alon gvili avatar' -Id 'avatarContent' -Variant small 28 | #> 29 | param( 30 | [Parameter ()][string]$Id = ([Guid]::NewGuid()).ToString(), 31 | [Parameter ()][string]$Image, 32 | [Parameter ()][string]$Alt, 33 | [Parameter ()][string]$ClassName, 34 | [Parameter ()][string]$Variant 35 | ) 36 | End { 37 | $Avatar = @{ 38 | type = 'mu-avatar' 39 | isPlugin = $true 40 | assetId = $MUAssetId 41 | 42 | id = $Id 43 | image = $Image 44 | alt = $Alt 45 | variant = $Variant 46 | className = $ClassName 47 | } 48 | $Avatar.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.Avatar") | Out-Null 49 | $Avatar 50 | } 51 | } -------------------------------------------------------------------------------- /src/Components/framework/error-card.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import makeStyles from '@mui/styles/makeStyles'; 3 | import Card from '@mui/material/Card'; 4 | import CardActions from '@mui/material/CardActions'; 5 | import CardContent from '@mui/material/CardContent'; 6 | import Typography from '@mui/material/Typography'; 7 | import UDIcon from '../../app/ud-icon'; 8 | 9 | const useStyles = makeStyles({ 10 | root: { 11 | minWidth: 275, 12 | }, 13 | bullet: { 14 | display: 'inline-block', 15 | margin: '0 2px', 16 | transform: 'scale(0.8)', 17 | }, 18 | title: { 19 | fontSize: 14, 20 | }, 21 | pos: { 22 | marginBottom: 12, 23 | }, 24 | }); 25 | 26 | export default function ErrorCard(props) { 27 | 28 | const classes = useStyles(); 29 | var icon = 30 | 31 | var records = null; 32 | if (!Array.isArray(props.errorRecords)) { 33 | records = Unknown error occurred 34 | } 35 | else { 36 | records = props.errorRecords.map(x => { 37 | return ( 38 | 39 | 40 | {x.message} 41 | 42 |
43 |             {x.location}
44 |           
45 |
46 | ) 47 | }) 48 | } 49 | 50 | return ( 51 | 52 | 53 | 54 | {icon} {props.title ? props.title : "One or more errors occurred"} 55 | 56 | {records} 57 | 58 | 59 | ) 60 | } -------------------------------------------------------------------------------- /src/Scripts/style.ps1: -------------------------------------------------------------------------------- 1 | function New-UDStyle { 2 | <# 3 | .SYNOPSIS 4 | Style a component with CSS styles. 5 | 6 | .DESCRIPTION 7 | Style a component with CSS styles. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Style 13 | The CSS style to apply 14 | 15 | .PARAMETER Tag 16 | The outer HTML tag to use. 17 | 18 | .PARAMETER Content 19 | The content of this style. 20 | 21 | .PARAMETER Sx 22 | A hashtable of theme-aware CSS properties. 23 | 24 | .PARAMETER Component 25 | The root component to apply the Sx component to. 26 | 27 | .EXAMPLE 28 | 29 | #> 30 | param( 31 | [Parameter()] 32 | [string]$Id = (New-Guid).ToString(), 33 | [Parameter(Mandatory = $true, ParameterSetName = 'Style')] 34 | [string]$Style, 35 | [Parameter(Mandatory = $true, ParameterSetName = 'Sx')] 36 | [Hashtable]$Sx, 37 | [Parameter(ParameterSetName = 'Sx')] 38 | [string]$Component, 39 | [Parameter()] 40 | [string]$Tag = 'div', 41 | [Parameter()] 42 | [ScriptBlock]$Content 43 | ) 44 | 45 | End { 46 | 47 | $Children = $null 48 | if ($null -ne $Content) { 49 | $Children = & $Content 50 | } 51 | 52 | @{ 53 | assetId = $AssetId 54 | isPlugin = $true 55 | type = "ud-style" 56 | id = $Id 57 | style = $Style 58 | tag = $Tag 59 | content = $Children 60 | sx = $Sx 61 | component = $Component 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Components/button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | import Button from '@mui/material/Button' 4 | import UdMuIcon from './icon' 5 | import { withComponentFeatures } from 'universal-dashboard' 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | button: { 10 | margin: theme.spacing(), 11 | }, 12 | leftIcon: { 13 | marginRight: theme.spacing(), 14 | }, 15 | rightIcon: { 16 | marginLeft: theme.spacing(), 17 | }, 18 | })) 19 | 20 | const UdButton = props => { 21 | const classes = useStyles() 22 | 23 | const handleClick = () => { 24 | if (props.onClick == null) return 25 | props.onClick(); 26 | } 27 | 28 | var icon = props.icon ? ( 29 | 35 | ) : null 36 | 37 | return ( 38 | 55 | ) 56 | } 57 | 58 | export default withComponentFeatures(UdButton) 59 | -------------------------------------------------------------------------------- /src/Components/timeline.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Timeline from '@mui/lab/Timeline'; 3 | import TimelineItem from '@mui/lab/TimelineItem'; 4 | import TimelineSeparator from '@mui/lab/TimelineSeparator'; 5 | import TimelineConnector from '@mui/lab/TimelineConnector'; 6 | import TimelineContent from '@mui/lab/TimelineContent'; 7 | import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent'; 8 | import TimelineDot from '@mui/lab/TimelineDot'; 9 | import { withComponentFeatures } from "universal-dashboard"; 10 | 11 | const UDTimeline = (props) => { 12 | let children = props.children; 13 | if (!Array.isArray(children)) { 14 | children = [children]; 15 | } 16 | 17 | return 18 | {children.map(x => { 19 | return ( 20 | 21 | {x.oppositeContent && ( 22 | 23 | {props.render(x.oppositeContent)} 24 | 25 | )} 26 | 27 | 28 | {x.icon && props.render(x.icon)} 29 | 30 | 31 | 32 | 33 | {props.render(x.content)} 34 | 35 | 36 | ) 37 | })} 38 | 39 | } 40 | 41 | export default withComponentFeatures(UDTimeline); -------------------------------------------------------------------------------- /src/Components/floating-action-button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import makeStyles from '@mui/styles/makeStyles'; 3 | import Fab from '@mui/material/Fab'; 4 | var classNames = require('classnames'); 5 | 6 | function onClick(props) { 7 | if (props.onClick) { 8 | UniversalDashboard.publish('element-event', { 9 | type: "clientEvent", 10 | eventId: props.onClick, 11 | eventName: 'onChange', 12 | eventData: '' 13 | }); 14 | } 15 | } 16 | 17 | export default function FloatingActionButton(props) { 18 | const useStyles = makeStyles(theme => ({ 19 | root: { 20 | '& > *': { 21 | margin: theme.spacing(1), 22 | }, 23 | }, 24 | extendedIcon: { 25 | marginRight: theme.spacing(1), 26 | }, 27 | fabRight: { 28 | position: 'absolute', 29 | bottom: theme.spacing(2), 30 | right: theme.spacing(2), 31 | }, 32 | fabLeft: { 33 | position: 'absolute', 34 | bottom: theme.spacing(2), 35 | left: theme.spacing(2), 36 | }, 37 | })); 38 | 39 | const classes = useStyles(); 40 | 41 | var icon = null; 42 | if (props.icon) { 43 | icon = UniversalDashboard.renderComponent(props.icon) 44 | } 45 | 46 | let position = null; 47 | if (props.position === 'bottomright') { 48 | position = classes.fabRight; 49 | } 50 | 51 | if (props.position === 'bottomleft') { 52 | position = classes.fabLeft; 53 | } 54 | 55 | return ( 56 |
57 | onClick(props)} size={props.size} id={props.id} color={props.themeColor}> 58 | {icon} 59 | 60 |
61 | ); 62 | } -------------------------------------------------------------------------------- /src/app/error-boundary.jsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertTitle, Button, Snackbar } from "@mui/material"; 2 | import React from "react"; 3 | import copy from 'copy-to-clipboard' 4 | 5 | export default class ErrorBoundary extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { hasError: false, error: null, errorInfo: null, snackbarOpen: false }; 9 | } 10 | 11 | componentDidCatch(error, errorInfo) { 12 | this.setState({ hasError: true, error, errorInfo }) 13 | } 14 | 15 | render() { 16 | const copyDetails = () => { 17 | copy(`Error rendering component (${this.props.componentType || 'unknown'})\r\n${this.state.error && this.state.error.toString()}\r\n${this.state.errorInfo.componentStack}`) 18 | this.setState({ snackbarOpen: true }) 19 | } 20 | 21 | if (this.state.hasError) { 22 | return <> 23 | 24 | {`Error rendering component (${this.props.componentType || 'unknown'})`} 25 |

This error is not expected. Please report it to Ironman Software.

26 | 27 |
28 | this.setState({ snackbarOpen: false })} 31 | autoHideDuration={6000} 32 | message="Error details copied" 33 | /> 34 | 35 | } 36 | 37 | return this.props.children; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Scripts/map/map.ps1: -------------------------------------------------------------------------------- 1 | function New-UDMap { 2 | param( 3 | [Parameter()] 4 | [string]$Id = (New-Guid).ToString(), 5 | [Parameter()] 6 | [float]$Longitude, 7 | [Parameter()] 8 | [float]$Latitude, 9 | [Parameter()] 10 | [int]$Zoom, 11 | [Parameter()] 12 | [string]$Height = '500px', 13 | [Parameter()] 14 | [string]$Width = '100%', 15 | [Parameter(Mandatory)] 16 | [object]$Endpoint, 17 | [ValidateSet("topright", "topleft", "bottomright", "bottomleft")] 18 | [string]$ZoomControlPosition = "topright", 19 | [ValidateSet("topright", "topleft", "bottomright", "bottomleft", "hide")] 20 | [string]$ScaleControlPosition = "bottomleft", 21 | [Parameter()] 22 | [Switch]$Animate, 23 | [Parameter()] 24 | [int]$MaxZoom = 18 25 | ) 26 | 27 | End { 28 | 29 | if ($Endpoint -is [scriptblock]) { 30 | $Endpoint = New-UDEndpoint -Endpoint $Endpoint -Id $Id 31 | } 32 | elseif ($Endpoint -isnot [UniversalDashboard.Models.Endpoint]) { 33 | throw "Endpoint must be a script block or UDEndpoint" 34 | } 35 | 36 | @{ 37 | assetId = $AssetId 38 | isPlugin = $true 39 | type = "ud-map" 40 | id = $Id 41 | 42 | longitude = $Longitude 43 | latitude = $Latitude 44 | zoom = $Zoom 45 | height = $Height 46 | width = $Width 47 | zoomControlPosition = $ZoomControlPosition 48 | animate = $Animate.IsPresent 49 | scaleControlPosition = $ScaleControlPosition 50 | maxZoom = $MaxZoom 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/Scripts/stack.ps1: -------------------------------------------------------------------------------- 1 | function New-UDStack { 2 | <# 3 | .SYNOPSIS 4 | The Stack component manages layout of immediate children along the vertical or horizontal axis with optional spacing and/or dividers between each child. 5 | 6 | .DESCRIPTION 7 | Stack is concerned with one-dimensional layouts, while Grid handles two-dimensional layouts. The default direction is column which stacks children vertically. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Children 13 | The children to stack. 14 | 15 | .PARAMETER Divider 16 | An optional divider component. 17 | 18 | .PARAMETER Spacing 19 | The spacing between the items. 20 | 21 | .PARAMETER Direction 22 | The stack direction. 23 | 24 | .EXAMPLE 25 | New-UDStack -Content { 26 | New-UDPaper 27 | New-UDPaper 28 | New-UDPaper 29 | } -Spacing 2 30 | 31 | Creates a stack of papers with 2 spacing. 32 | #> 33 | param( 34 | [Parameter()] 35 | [String]$Id = ([Guid]::NewGuid()), 36 | [Parameter(Mandatory)] 37 | [Alias("Content")] 38 | [scriptblock]$Children, 39 | [Parameter()] 40 | [scriptblock]$Divider = {}, 41 | [Parameter()] 42 | [int]$Spacing, 43 | [Parameter()] 44 | [ValidateSet('row', 'column')] 45 | [string]$Direction = 'row' 46 | ) 47 | 48 | @{ 49 | id = $id 50 | assetId = $MUAssetId 51 | isPlugin = $true 52 | type = "mu-stack" 53 | 54 | children = & $Children 55 | divider = & $Divider 56 | spacing = $Spacing 57 | direction = $Direction.ToLower() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Components/editor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withComponentFeatures } from 'universal-dashboard'; 3 | 4 | import Embed from '@editorjs/embed' 5 | import Table from '@editorjs/table' 6 | import Paragraph from '@editorjs/paragraph' 7 | import List from '@editorjs/list' 8 | import Warning from '@editorjs/warning' 9 | import Code from '@editorjs/code' 10 | import LinkTool from '@editorjs/link' 11 | import Image from '@editorjs/image' 12 | import Raw from '@editorjs/raw' 13 | import Header from '@editorjs/header' 14 | import Quote from '@editorjs/quote' 15 | import Marker from '@editorjs/marker' 16 | import CheckList from '@editorjs/checklist' 17 | import Delimiter from '@editorjs/delimiter' 18 | import InlineCode from '@editorjs/inline-code' 19 | import SimpleImage from '@editorjs/simple-image' 20 | import EditorJs from 'react-editor-js'; 21 | import Parser from '@son_xx/editor-js-parser' 22 | 23 | const EDITOR_JS_TOOLS = { 24 | embed: Embed, 25 | table: Table, 26 | paragraph: Paragraph, 27 | list: List, 28 | warning: Warning, 29 | code: Code, 30 | linkTool: LinkTool, 31 | image: Image, 32 | raw: Raw, 33 | header: Header, 34 | quote: Quote, 35 | marker: Marker, 36 | checklist: CheckList, 37 | delimiter: Delimiter, 38 | inlineCode: InlineCode, 39 | simpleImage: SimpleImage 40 | } 41 | 42 | const UDEditor = (props) => { 43 | const save = (api, data) => { 44 | if (props.onChange) { 45 | let myData = data; 46 | if (props.format === "html") { 47 | myData = Parser(data.blocks) 48 | } 49 | props.onChange(myData); 50 | } 51 | } 52 | 53 | return ( 54 | <> 55 | 56 | 57 | ) 58 | } 59 | 60 | export default withComponentFeatures(UDEditor); -------------------------------------------------------------------------------- /src/Scripts/badge.ps1: -------------------------------------------------------------------------------- 1 | function New-UDBadge { 2 | param( 3 | [Parameter ()] 4 | [string]$Id = ([Guid]::NewGuid()).ToString(), 5 | [Parameter()] 6 | [ValidateSet("defeault", 'primary', 'secondary', 'error', 'info', 'success', 'warning')] 7 | [string]$Color = 'default', 8 | [Parameter(Mandatory)] 9 | [Alias("Content")] 10 | [ScriptBlock]$Children = {}, 11 | [Parameter()] 12 | [scriptblock]$BadgeContent = {}, 13 | [Parameter()] 14 | [Switch]$Invisible, 15 | [Parameter()] 16 | [int]$Max = 99, 17 | [Parameter()] 18 | [ValidateSet('circular', 'rectangular')] 19 | [string]$Overlap = 'rectangular', 20 | [Parameter()] 21 | [Switch]$ShowZero, 22 | [Parameter()] 23 | [ValidateSet('standard', 'dot')] 24 | [string]$Variant = 'standard', 25 | [Parameter()] 26 | [ValidateSet('topright', 'topleft', 'bottomright', 'bottomleft')] 27 | [string]$Location = 'topright' 28 | ) 29 | 30 | @{ 31 | type = 'mu-badge' 32 | isPlugin = $true 33 | assetId = $MUAssetId 34 | id = $Id 35 | color = $Color.ToLower() 36 | children = & $Children 37 | badgeContent = & $BadgeContent 38 | invisible = $Invisible.IsPresent 39 | max = $Max 40 | overlap = $Overlap.ToLower() 41 | showZero = $ShowZero.IsPresent 42 | variant = $Variant.ToLower() 43 | location = $Location.ToLower() 44 | } 45 | } 46 | 47 | <# 48 | New-UDBadge -Content { 49 | New-UDIcon -Icon 'User' -Size 2x 50 | } -BadgeContent { 51 | 100 52 | } -Color primary 53 | 54 | #> -------------------------------------------------------------------------------- /src/Scripts/debugdashboard.ps1: -------------------------------------------------------------------------------- 1 | function Debug-PSUDashboard { 2 | <# 3 | .SYNOPSIS 4 | Provides a utility function for debugging scripts running PowerShell Universal Dashboard. 5 | 6 | .DESCRIPTION 7 | Provides a utility function for debugging scripts running PowerShell Universal Dashboard. This cmdlet integrates with the VS Code PowerShell Universal extension to automatically connect the debugger to endpoints running in UD. 8 | 9 | .EXAMPLE 10 | Creates an element that invokes the Debug-PSUDashboard cmdlet. 11 | 12 | New-UDElement -Tag div -Endpoint { 13 | Debug-PSUDashboard 14 | } 15 | #> 16 | [CmdletBinding()] 17 | param() 18 | 19 | $DebugPreference = 'continue' 20 | 21 | $Runspace = ([runspace]::DefaultRunspace).id 22 | 23 | Show-UDModal -Header { 24 | New-UDTypography -Text 'Debug Dashboard' -Variant h4 25 | } -Content { 26 | Write-Debug "IN DEBUG MODE: Enter-PSHostProcess -Id $PID then Debug-Runspace -Id $Runspace" 27 | New-UDTypography -Text "You can run the following PowerShell commands in any PowerShell host to debug this dashboard." 28 | New-UDElement -Tag 'pre' -Content { 29 | "Enter-PSHostProcess -Id $PID`r`nDebug-Runspace -Id $Runspace" 30 | } 31 | } -Footer { 32 | New-UDLink -Children { 33 | New-UDButton -Text 'Debug with VS Code' 34 | } -Url "vscode://ironmansoftware.powershell-universal/debug?PID=$PID&RS=$Runspace" 35 | New-UDLink -Children { 36 | New-UDButton -Text 'Debug with VS Code Insiders' 37 | } -Url "vscode-insiders://ironmansoftware.powershell-universal/debug?PID=$PID&RS=$Runspace" 38 | New-UDButton -Text 'Close' -OnClick { Hide-UDModal } 39 | } 40 | 41 | Wait-Debugger 42 | } -------------------------------------------------------------------------------- /src/Components/table/components/baseTableFooter.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import React from "react" 3 | import { TableFooter, TablePagination, TableRow, LinearProgress } from "@mui/material"; 4 | import TablePaginationActions from "../v2/TablePaginationActions"; 5 | import { onlyUnique } from "../v2/utilities"; 6 | 7 | export default function BaseTableFooter({ 8 | userPageSize, 9 | userPageSizeOptions, 10 | colSpan, 11 | count, 12 | pageIndex, 13 | gotoPage, 14 | setPageSize, 15 | pageSize, 16 | isVisible, 17 | loading, 18 | disablePageSizeAll, 19 | }) { 20 | function onChangePage(_, newPage) { 21 | gotoPage(newPage); 22 | } 23 | 24 | function onChangeRowsPerPage(event) { 25 | setPageSize(Number(event.target.value)); 26 | } 27 | 28 | var rowsPerPageOptions = [ 29 | userPageSize, 30 | ...userPageSizeOptions, 31 | ] 32 | 33 | if (!disablePageSizeAll) { 34 | rowsPerPageOptions.push({ label: "All", value: count }) 35 | } 36 | 37 | return ( 38 | isVisible && 39 | 40 | {loading ? : } 41 | {!loading && count === 0 &&
No data
} 42 | a - b).filter(onlyUnique)} 45 | colSpan={colSpan} 46 | count={count} 47 | rowsPerPage={pageSize} 48 | page={pageIndex} 49 | onPageChange={onChangePage} 50 | onRowsPerPageChange={onChangeRowsPerPage} 51 | ActionsComponent={TablePaginationActions} 52 | /> 53 |
54 |
55 | ); 56 | } -------------------------------------------------------------------------------- /src/Components/switch.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import Switch from '@mui/material/Switch'; 3 | import Grid from '@mui/material/Grid'; 4 | import { withComponentFeatures } from 'universal-dashboard'; 5 | import { FormContext } from './form'; 6 | 7 | const UDSwitchWithContext = (props) => { 8 | return ( 9 | 10 | { 11 | ({ onFieldChange }) => 12 | } 13 | 14 | ) 15 | } 16 | 17 | const UDSwitch = (props) => { 18 | 19 | const onChange = (event) => { 20 | props.onFieldChange({ id: props.id, value: event.target.checked }) 21 | props.setState({ checked: event.target.checked }); 22 | 23 | if (props.onChange) { 24 | props.onChange(event.target.checked) 25 | } 26 | } 27 | 28 | useEffect(() => { 29 | props.onFieldChange({ id: props.id, value: props.checked }); 30 | return () => { } 31 | }, true) 32 | 33 | const switchC = onChange(event)} 40 | disabled={props.disabled} 41 | /> 42 | 43 | return props.checkedLabel || props.uncheckedLabel || props.label ? ( 44 | 45 | {props.uncheckedLabel} 46 | 47 | {switchC} 48 | 49 | {props.checkedLabel || props.label} 50 | 51 | ) : switchC 52 | } 53 | 54 | export default withComponentFeatures(UDSwitchWithContext); 55 | 56 | -------------------------------------------------------------------------------- /src/Components/map/cluster-layer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UDMarker from './marker'; 3 | import MarkerClusterGroup from './Cluster'; 4 | import {isGuid} from './utils'; 5 | 6 | export default class UDClusterLayer extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | markers: props.markers 13 | } 14 | } 15 | 16 | componentWillMount() { 17 | if (!isGuid(this.props.id)) { 18 | this.pubSubToken = UniversalDashboard.subscribe(this.props.id, this.onIncomingEvent.bind(this)); 19 | } 20 | } 21 | 22 | onIncomingEvent(eventName, event) { 23 | if (event.type === "removeElement") { 24 | this.props.onRemove(event.componentId); 25 | } 26 | 27 | if (event.type === "clearElement") { 28 | this.setState({ 29 | markers: [] 30 | }) 31 | } 32 | 33 | if (event.type === 'addElement') { 34 | var markers = this.state.markers; 35 | 36 | var content = event.elements; 37 | if (!Array.isArray(content)) { 38 | content = [content] 39 | } 40 | 41 | markers = markers.concat(content); 42 | 43 | this.setState({ 44 | markers 45 | }) 46 | } 47 | } 48 | 49 | onRemoveMarker(id) { 50 | var markers = this.state.markers.filter(x => x.id !== id); 51 | 52 | this.setState({ 53 | markers 54 | }) 55 | } 56 | 57 | render() { 58 | return ( 59 | 60 | {this.state.markers.map(x => )} 61 | 62 | ) 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/Scripts/timepicker.ps1: -------------------------------------------------------------------------------- 1 | function New-UDTimePicker { 2 | <# 3 | .SYNOPSIS 4 | Creates a time picker. 5 | 6 | .DESCRIPTION 7 | Creates a time picker. This component can be used stand alone or within New-UDForm. 8 | 9 | .PARAMETER Id 10 | The ID of the component. It defaults to a random GUID. 11 | 12 | .PARAMETER Label 13 | The label to show with the time picker. 14 | 15 | .PARAMETER OnChange 16 | A script block to call when the time is changed. The $EventData variable contains the currently selected time. 17 | 18 | .PARAMETER Value 19 | The current value of the time picker. 20 | 21 | .PARAMETER Locale 22 | Change the language of the time picker. 23 | 24 | .EXAMPLE 25 | Creates a new time picker 26 | 27 | New-UDTimePicker -Id 'timePicker' 28 | #> 29 | param( 30 | [Parameter()] 31 | [string]$Id = [Guid]::NewGuid().ToString(), 32 | [Parameter()] 33 | [string]$Label, 34 | [Parameter()] 35 | [Endpoint]$OnChange, 36 | [Parameter()] 37 | [string]$Value, 38 | [Parameter()] 39 | [ValidateSet("en", "de", 'ru', 'fr')] 40 | [string]$Locale = "en", 41 | [Parameter()] 42 | [string]$ClassName, 43 | [Parameter()] 44 | [Switch]$DisableAmPm 45 | ) 46 | 47 | if ($OnChange) { 48 | $OnChange.Register($Id, $PSCmdlet) 49 | } 50 | 51 | @{ 52 | id = $Id 53 | type = 'mu-timepicker' 54 | asset = $MUAssetId 55 | isPlugin = $true 56 | 57 | onChange = $OnChange 58 | value = $Value 59 | label = $Label 60 | locale = $Locale.ToLower() 61 | className = $ClassName 62 | ampm = -not ($DisableAmPm.IsPresent) 63 | } 64 | } -------------------------------------------------------------------------------- /src/Components/card-expand.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import withStyles from '@mui/styles/withStyles'; 3 | import classNames from "classnames"; 4 | import { ReactInterval } from "react-interval/lib/Component"; 5 | 6 | const styles = theme => ({ 7 | content: { 8 | display: "flex" 9 | } 10 | }); 11 | 12 | class UDCardExpand extends React.Component { 13 | state = { 14 | content: this.props.content, 15 | }; 16 | 17 | onLoadData = () => { 18 | if (!this.props.isEndpoint) { 19 | this.setState({ 20 | content: this.props.content 21 | }) 22 | } 23 | else { 24 | UniversalDashboard.get( 25 | `/api/internal/component/element/${this.props.id}`, data => { 26 | data.error ? 27 | this.setState({ hasError: true, error: data.error, content: data, }) 28 | : this.setState({ content: data, loading: false }) 29 | } 30 | ) 31 | } 32 | } 33 | 34 | componentWillMount = () => { 35 | this.onLoadData(); 36 | } 37 | 38 | render() { 39 | const { 40 | className, 41 | classes, 42 | id, 43 | style, 44 | isEndpoint, 45 | refreshInterval, 46 | autoRefresh 47 | } = this.props; 48 | 49 | const { content } = this.state; 50 | 51 | return ( 52 | 53 |
57 | {UniversalDashboard.renderComponent(content)} 58 |
59 | 60 | {isEndpoint ? 61 | : null} 65 | 66 |
67 | ); 68 | } 69 | } 70 | 71 | export default withStyles(styles)(UDCardExpand); 72 | -------------------------------------------------------------------------------- /examples/ping-grid.ps1: -------------------------------------------------------------------------------- 1 | Import-Module UniversalDashboard -Force 2 | 3 | # First Let's define a list of servers to monitor 4 | $MonitoredServersSetup = @() 5 | $Cache:MonitoredServers 6 | $Servers = @("www.google.com", "www.github.com", "8.8.8.8","192.168.0.1") 7 | 8 | # Create some powershell objects to feed into our UI / Update Later 9 | $Servers | ForEach-Object{ 10 | $ServerObject = [PSCustomObject]@{ 11 | Server = $_ 12 | LastUp = (Get-Date) 13 | Status = $false 14 | } 15 | $MonitoredServersSetup += $ServerObject 16 | } 17 | $Cache:MonitoredServers = $MonitoredServersSetup 18 | 19 | # Setup new Dashboard with Grid 20 | Get-UDDashboard | Stop-UDDashboard 21 | $Dashboard = New-UDDashboard -Title "PowerShell Ping Buddy" -Content { 22 | 23 | New-UDGrid -Id "PingStatusGrid" -Title "Ping Buddy" -Headers @("Server", "LastUp","Status") -Properties @("Server","LastUp","Status") -Endpoint { 24 | $Cache:MonitoredServers | Out-UDGridData 25 | } 26 | 27 | } 28 | 29 | # New 30 second schedule 30 | $30SecSchedule = New-UDEndpointSchedule -Every 30 -Second 31 | 32 | # New Endpoint to ping the server address of my "MonitoredServers Object - we'll also refresh the UD grid." 33 | $PingEndpoint = New-UDEndpoint -Schedule $30SecSchedule -Endpoint { 34 | 35 | $Cache:MonitoredServers | ForEach-Object{ 36 | $pingResult = Test-NetConnection $_.Server 37 | if($pingResult.PingSucceeded -eq $true) 38 | { 39 | $_.Status = $true 40 | $_.LastUp = (Get-Date) 41 | } 42 | else { 43 | $_.Status = $false 44 | } 45 | } 46 | 47 | Sync-UDElement -Id "PingStatusGrid" -Broadcast 48 | 49 | } 50 | 51 | Get-UDDashboard -Name 'PowerShell Ping Buddy' | Stop-UDDashboard 52 | Start-UDDashboard -Dashboard $Dashboard -Port 10000 -Endpoint $PingEndpoint 53 | Start http://localhost:10000 -------------------------------------------------------------------------------- /src/Scripts/tooltip.ps1: -------------------------------------------------------------------------------- 1 | function New-UDTooltip { 2 | <# 3 | .SYNOPSIS 4 | A tooltip component. 5 | 6 | .DESCRIPTION 7 | A tooltip component. Tooltips can be placed over an other component to display a popup when the user hovers over the nested component. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER Place 13 | Where to place the tooltip. 14 | 15 | .PARAMETER Type 16 | The type of tooltip. 17 | 18 | .PARAMETER Effect 19 | An effect to apply to the tooltip. 20 | 21 | .PARAMETER TooltipContent 22 | Content to display within the tooltip. 23 | 24 | .PARAMETER Content 25 | Content that activates the tooltip when hovered. 26 | 27 | .EXAMPLE 28 | A simple tooltip. 29 | 30 | New-UDTooltip -Content { 31 | New-UDTypography -Text 'Hover me' 32 | } -TooltipContent { 33 | New-UDTypography -Text 'I'm a tooltip' 34 | } 35 | #> 36 | param( 37 | [Parameter()] 38 | [string]$Id = [Guid]::NewGuid(), 39 | [Parameter()] 40 | [ValidateSet("top", "bottom", "left", "right")] 41 | [string]$Place = "top", 42 | [Parameter()] 43 | [ValidateSet("dark", "success", "warning", "error", "info", "light")] 44 | [string]$Type = "dark", 45 | [Parameter()] 46 | [ValidateSet("float", "solid")] 47 | [string]$Effect, 48 | [Parameter(Mandatory)] 49 | [ScriptBlock]$TooltipContent, 50 | [Parameter(Mandatory)] 51 | [ScriptBlock]$Content 52 | ) 53 | 54 | @{ 55 | type = "ud-tooltip" 56 | tooltipType = $Type 57 | effect = $Effect 58 | place = $Place 59 | id = $Id 60 | tooltipContent = New-UDErrorBoundary -Content $TooltipContent 61 | content = New-UDErrorBoundary -Content $Content 62 | } 63 | } -------------------------------------------------------------------------------- /src/Scripts/layout.ps1: -------------------------------------------------------------------------------- 1 | function New-UDLayout { 2 | <# 3 | .SYNOPSIS 4 | Create a layout based on the number of components within the layout. 5 | 6 | .DESCRIPTION 7 | Create a layout based on the number of components within the layout. The component wraps New-UDRow and New-UDColumn to automatically layout components in the content. 8 | 9 | .PARAMETER Columns 10 | The number of columns in this layout. 11 | 12 | .PARAMETER Content 13 | The content for this layout. 14 | 15 | .EXAMPLE 16 | New-UDLayout -Columns 2 -Content { 17 | New-UDTypography "Row 1, Col 1" 18 | New-UDTypography "Row 1, Col 2" 19 | New-UDTypography "Row 2, Col 1" 20 | New-UDTypography "Row 2, Col 2" 21 | } 22 | #> 23 | param( 24 | [Parameter(Mandatory = $true)] 25 | [ValidateRange(1, 12)] 26 | [int]$Columns, 27 | [Parameter(Mandatory = $true)] 28 | [ScriptBlock]$Content 29 | ) 30 | 31 | $Components = $Content.Invoke() 32 | 33 | if ($Columns -eq 1) 34 | { 35 | $LargeColumnSize = 12 36 | $MediumColumnSize = 12 37 | $SmallColumnSize = 12 38 | } 39 | else 40 | { 41 | $LargeColumnSize = 12 / $Columns 42 | $MediumColumnSize = 12 / ($Columns / 2) 43 | $SmallColumnSize = 12 44 | } 45 | 46 | $Offset = 0 47 | $ComponentCount = ($Components | Measure-Object).Count 48 | 49 | while ($Offset -lt $ComponentCount) { 50 | $ColumnObjects = $Components | Select-Object -Skip $Offset -First $Columns | ForEach-Object { 51 | New-UDColumn -SmallSize $SmallColumnSize -MediumSize $MediumColumnSize -LargeSize $LargeColumnSize -Content { 52 | $_ 53 | } 54 | } 55 | 56 | New-UDRow -Columns { 57 | $ColumnObjects 58 | } 59 | 60 | $Offset += $Columns 61 | } 62 | } -------------------------------------------------------------------------------- /src/Components/map/heatmap-layer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HeatmapLayer from './HeatmapLayer.js'; 3 | import {isGuid} from './utils'; 4 | 5 | export default class UDHeatmap extends React.Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { 11 | points: props.points 12 | } 13 | } 14 | 15 | componentWillMount() { 16 | if (!isGuid(this.props.id)) { 17 | this.pubSubToken = UniversalDashboard.subscribe(this.props.id, this.onIncomingEvent.bind(this)); 18 | } 19 | } 20 | 21 | onIncomingEvent(eventName, event) { 22 | if (event.type === "removeElement") { 23 | this.props.onRemove(event.componentId); 24 | } 25 | 26 | if (event.type === "clearElement") { 27 | this.setState({ 28 | points: [] 29 | }) 30 | } 31 | 32 | if (event.type === "addElement") { 33 | var points = this.state.points.concat(event.elements); 34 | 35 | this.setState({ 36 | points 37 | }) 38 | } 39 | } 40 | 41 | render() { 42 | return ( 43 | m[1]} 48 | latitudeExtractor={m => m[0]} 49 | intensityExtractor={m => parseFloat(m[2])} 50 | //max={this.props.maxIntensity} 51 | //radius={this.props.radius} 52 | maxZoom={this.props.maxZoom} 53 | minOpacity={this.props.minOpacity} 54 | //blur={this.props.blur} 55 | gradient={this.props.gradient} 56 | onReportBounds={this.props.onReportBounds} 57 | /> 58 | ) 59 | } 60 | } -------------------------------------------------------------------------------- /src/Components/map/UDLayerControlBaseLayer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { LayersControl } from 'react-leaflet'; 3 | import { isGuid } from './utils'; 4 | 5 | export default class UDLayerControlBaseLayer extends React.Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { 11 | content: props.content 12 | } 13 | } 14 | 15 | componentWillMount() { 16 | if (!isGuid(this.props.id)) { 17 | this.pubSubToken = UniversalDashboard.subscribe(this.props.id, this.onIncomingEvent.bind(this)); 18 | } 19 | } 20 | 21 | onRemoveChild(id) { 22 | var content = this.state.content; 23 | if (!Array.isArray(content)) { 24 | content = [content]; 25 | } 26 | 27 | content = content.filter(x => x.id !== id); 28 | 29 | this.setState({ 30 | content 31 | }) 32 | } 33 | 34 | onIncomingEvent(eventName, event) { 35 | if (event.type === "removeElement") { 36 | this.props.onRemove(this.props.id); 37 | } 38 | 39 | if (event.type === "addElement") { 40 | var content = this.state.content; 41 | if (!Array.isArray(content)) { 42 | content = [content]; 43 | } 44 | 45 | content = content.concat(event.elements); 46 | 47 | this.setState({ 48 | content 49 | }) 50 | } 51 | } 52 | 53 | render() { 54 | var content = this.state.content; 55 | if (!Array.isArray(content)) { 56 | content = [content]; 57 | } 58 | 59 | return 60 | {content.map(x => { 61 | x.onRemove = this.onRemoveChild.bind(this); 62 | return UniversalDashboard.renderComponent(x); 63 | })} 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /src/Components/datepicker.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { withComponentFeatures } from 'universal-dashboard'; 3 | import { FormContext } from './form'; 4 | import 'date-fns'; 5 | 6 | import AdapterDateFns from '@mui/lab/AdapterDateFns'; 7 | import LocalizationProvider from '@mui/lab/LocalizationProvider'; 8 | import DatePicker from '@mui/lab/DatePicker'; 9 | import TextField from "@mui/material/TextField"; 10 | 11 | 12 | import frLocale from "date-fns/locale/fr"; 13 | import deLocale from "date-fns/locale/de"; 14 | import enLocale from "date-fns/locale/en-US"; 15 | import ruLocale from "date-fns/locale/ru"; 16 | 17 | const locales = { 18 | "fr": frLocale, 19 | "de": deLocale, 20 | "en": enLocale, 21 | "ru": ruLocale 22 | } 23 | 24 | const DatePickerWithContext = (props) => { 25 | return ( 26 | 27 | { 28 | ({ onFieldChange }) => 29 | } 30 | 31 | ) 32 | } 33 | 34 | const DateTimePicker = (props) => { 35 | 36 | const onChange = (value) => { 37 | props.onFieldChange({ id: props.id, value }); 38 | props.setState({ value }); 39 | if (props.onChange) { 40 | props.onChange(value); 41 | } 42 | } 43 | 44 | useEffect(() => { 45 | props.onFieldChange({ id: props.id, value: props.value }); 46 | return () => { } 47 | }, true) 48 | 49 | return ( 50 | 51 | onChange(value)} 55 | renderInput={(params) => } 56 | /> 57 | 58 | ) 59 | } 60 | 61 | export default withComponentFeatures(DatePickerWithContext); -------------------------------------------------------------------------------- /src/Scripts/icon-button.ps1: -------------------------------------------------------------------------------- 1 | function New-UDIconButton { 2 | <# 3 | .SYNOPSIS 4 | Creates a button with an icon. 5 | 6 | .DESCRIPTION 7 | Creates a button with an icon. 8 | 9 | .PARAMETER Id 10 | The ID of the component. It defaults to a random GUID. 11 | 12 | .PARAMETER Icon 13 | The icon to display within the button. 14 | 15 | .PARAMETER OnClick 16 | A script block to execute when the button is clicked. 17 | 18 | .PARAMETER Disabled 19 | Whether the button is disabled. 20 | 21 | .PARAMETER Href 22 | A link to navigate to when the button is clicked. 23 | 24 | .PARAMETER Style 25 | The CSS sytle to apply to the icon. 26 | 27 | .EXAMPLE 28 | Creates an icon button with a user icon with a custom style. 29 | 30 | New-UDIconButton -Icon (New-UDIcon -Icon user -Size sm -Style @{color = '#000'}) -Id 'iconButtonContent' 31 | #> 32 | param( 33 | [Parameter()] 34 | [string]$Id = ([Guid]::NewGuid()).ToString(), 35 | [Parameter ()] 36 | [PSTypeName('UniversalDashboard.Icon')]$Icon, 37 | [Parameter ()] 38 | [Endpoint] $OnClick, 39 | [Parameter ()] 40 | [Switch] $Disabled, 41 | [Parameter ()] 42 | [string] $Href, 43 | [Parameter ()] 44 | [Hashtable] $Style, 45 | [Parameter()] 46 | [string]$ClassName 47 | 48 | 49 | ) 50 | 51 | End { 52 | if ($OnClick) { 53 | $OnClick.Register($Id, $PSCmdlet) 54 | } 55 | 56 | @{ 57 | type = 'mu-icon-button' 58 | isPlugin = $true 59 | assetId = $MUAssetId 60 | id = $Id 61 | disabled = $Disabled.IsPresent 62 | style = $Style 63 | onClick = $OnClick 64 | icon = $Icon 65 | href = $Href 66 | className = $ClassName 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Components/map/UDLayerControlOverlay.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { LayersControl } from 'react-leaflet'; 4 | import { isGuid } from './utils'; 5 | 6 | 7 | export class UDLayerControlOverlay extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | content: props.content 14 | } 15 | } 16 | 17 | componentWillMount() { 18 | if (!isGuid(this.props.id)) { 19 | this.pubSubToken = UniversalDashboard.subscribe(this.props.id, this.onIncomingEvent.bind(this)); 20 | } 21 | } 22 | 23 | onRemoveChild(id) { 24 | var content = this.state.content; 25 | if (!Array.isArray(content)) { 26 | content = [content]; 27 | } 28 | 29 | content = content.filter(x => x.id !== id); 30 | 31 | this.setState({ 32 | content 33 | }) 34 | } 35 | 36 | onIncomingEvent(eventName, event) { 37 | if (event.type === "removeElement") { 38 | this.props.onRemove(this.props.id); 39 | } 40 | 41 | if (event.type === "addElement") { 42 | var content = this.state.content; 43 | if (!Array.isArray(content)) { 44 | content = [content]; 45 | } 46 | 47 | content = content.concat(event.elements); 48 | 49 | this.setState({ 50 | content 51 | }) 52 | } 53 | } 54 | 55 | render() { 56 | var content = this.state.content; 57 | if (!Array.isArray(content)) { 58 | content = [content]; 59 | } 60 | 61 | return 62 | {content.map(x => { 63 | x.onRemove = this.onRemoveChild.bind(this); 64 | x.onReportBounds = this.props.onReportBounds; 65 | return UniversalDashboard.renderComponent(x); 66 | })} 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /src/app/polyfills.js: -------------------------------------------------------------------------------- 1 | if (typeof Object.assign != 'function') { 2 | Object.assign = function(target) { 3 | 'use strict'; 4 | if (target == null) { 5 | throw new TypeError('Cannot convert undefined or null to object'); 6 | } 7 | 8 | target = Object(target); 9 | for (var index = 1; index < arguments.length; index++) { 10 | var source = arguments[index]; 11 | if (source != null) { 12 | for (var key in source) { 13 | if (Object.prototype.hasOwnProperty.call(source, key)) { 14 | target[key] = source[key]; 15 | } 16 | } 17 | } 18 | } 19 | return target; 20 | }; 21 | } 22 | 23 | if (!String.prototype.startsWith) { 24 | String.prototype.startsWith = function(searchString, position){ 25 | return this.substr(position || 0, searchString.length) === searchString; 26 | }; 27 | } 28 | 29 | if (!Array.prototype.fill) { 30 | Object.defineProperty(Array.prototype, 'fill', { 31 | value: function(value) { 32 | 33 | // Steps 1-2. 34 | if (this == null) { 35 | throw new TypeError('this is null or not defined'); 36 | } 37 | 38 | var O = Object(this); 39 | 40 | // Steps 3-5. 41 | var len = O.length >>> 0; 42 | 43 | // Steps 6-7. 44 | var start = arguments[1]; 45 | var relativeStart = start >> 0; 46 | 47 | // Step 8. 48 | var k = relativeStart < 0 ? 49 | Math.max(len + relativeStart, 0) : 50 | Math.min(relativeStart, len); 51 | 52 | // Steps 9-10. 53 | var end = arguments[2]; 54 | var relativeEnd = end === undefined ? 55 | len : end >> 0; 56 | 57 | // Step 11. 58 | var final = relativeEnd < 0 ? 59 | Math.max(len + relativeEnd, 0) : 60 | Math.min(relativeEnd, len); 61 | 62 | // Step 12. 63 | while (k < final) { 64 | O[k] = value; 65 | k++; 66 | } 67 | 68 | // Step 13. 69 | return O; 70 | } 71 | }); 72 | } -------------------------------------------------------------------------------- /src/Scripts/interactive/Show-Modal.ps1: -------------------------------------------------------------------------------- 1 | function Show-UDModal { 2 | <# 3 | .SYNOPSIS 4 | Show a modal. 5 | 6 | .DESCRIPTION 7 | Show a modal. 8 | 9 | .PARAMETER FullScreen 10 | Create a full screen modal. 11 | 12 | .PARAMETER Footer 13 | The footer components for the modal. 14 | 15 | .PARAMETER Header 16 | The header components for the modal. 17 | 18 | .PARAMETER Content 19 | The content of the modal. 20 | 21 | .PARAMETER Persistent 22 | Whether the modal can be closed by clicking outside of it. 23 | 24 | .PARAMETER FullWidth 25 | Whether the modal is full width. 26 | 27 | .PARAMETER MaxWidth 28 | The max width of the modal. 29 | 30 | .EXAMPLE 31 | New-UDButton -Text 'Click Me' -OnClick { 32 | Show-UDModal -Content { 33 | New-UDTypography -Text "Hello World" 34 | } 35 | } 36 | #> 37 | param( 38 | [Parameter()] 39 | [Switch]$FullScreen, 40 | [Parameter()] 41 | [ScriptBlock]$Footer = {}, 42 | [Parameter()] 43 | [ScriptBlock]$Header = {}, 44 | [Parameter()] 45 | [ScriptBlock]$Content = {}, 46 | [Parameter()] 47 | [Switch]$Persistent, 48 | [Parameter()] 49 | [Switch]$FullWidth, 50 | [Parameter()] 51 | [ValidateSet("xs", "sm", "md", "lg", "xl")] 52 | [string]$MaxWidth 53 | ) 54 | 55 | $Modal = @{ 56 | dismissible = -not $Persistent.IsPresent 57 | maxWidth = $MaxWidth 58 | fullWidth = $FullWidth.IsPresent 59 | fullScreen = $FullScreen.IsPresent 60 | } 61 | 62 | if ($null -ne $Footer) { 63 | $Modal['footer'] = & $Footer 64 | } 65 | 66 | if ($null -ne $Header) { 67 | $Modal['header'] = & $Header 68 | } 69 | 70 | if ($null -ne $Content) { 71 | $Modal['content'] = & $Content 72 | } 73 | 74 | $DashboardHub.SendWebSocketMessage($ConnectionId, "showModal", $modal) 75 | } 76 | -------------------------------------------------------------------------------- /src/Components/checkbox.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import Checkbox from "@mui/material/Checkbox"; 3 | import CheckBoxIcon from "@mui/icons-material/CheckBox" 4 | import CheckBoxIconBlank from "@mui/icons-material/CheckBoxOutlineBlank" 5 | import FormControlLabel from "@mui/material/FormControlLabel" 6 | import classNames from "classnames"; 7 | import { FormContext } from './form'; 8 | import { withComponentFeatures } from 'universal-dashboard'; 9 | 10 | const UDCheckboxWithContext = (props) => { 11 | return ( 12 | 13 | { 14 | ({ onFieldChange }) => 15 | } 16 | 17 | ); 18 | } 19 | 20 | const UDCheckbox = (props) => { 21 | const onChange = (event) => { 22 | props.onFieldChange({ id: props.id, value: event.target.checked }); 23 | 24 | props.setState({ checked: event.target.checked }) 25 | 26 | if (props.onChange) { 27 | props.onChange(event.target.checked) 28 | } 29 | } 30 | 31 | useEffect(() => { 32 | props.onFieldChange({ id: props.id, value: props.checked }); 33 | return () => { } 34 | }, true) 35 | 36 | return ( 37 | onChange(event)} 45 | value={props.checked} 46 | style={!props.disabled ? { ...props.style } : { color: null }} 47 | color={props.color} 48 | icon={props.icon ? props.render(props.icon) : } 49 | checkedIcon={props.checkedIcon ? props.render(props.checkedIcon) : } 50 | /> 51 | } 52 | label={!props.label ? '' : props.label} 53 | labelPlacement={props.labelPlacement} 54 | /> 55 | ) 56 | } 57 | 58 | export default withComponentFeatures(UDCheckboxWithContext); -------------------------------------------------------------------------------- /src/Components/timepicker.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { withComponentFeatures } from 'universal-dashboard'; 3 | import { FormContext } from './form'; 4 | import TextField from "@mui/material/TextField"; 5 | import TimePicker from '@mui/lab/TimePicker'; 6 | import AdapterDateFns from '@mui/lab/AdapterDateFns'; 7 | import LocalizationProvider from '@mui/lab/LocalizationProvider'; 8 | 9 | import 'date-fns'; 10 | import frLocale from "date-fns/locale/fr"; 11 | import deLocale from "date-fns/locale/de"; 12 | import enLocale from "date-fns/locale/en-US"; 13 | import ruLocale from "date-fns/locale/ru"; 14 | 15 | const locales = { 16 | "fr": frLocale, 17 | "de": deLocale, 18 | "en": enLocale, 19 | "ru": ruLocale 20 | } 21 | 22 | const TimePickerWithContext = props => { 23 | return ( 24 | 25 | { 26 | ({ onFieldChange }) => 27 | } 28 | 29 | ) 30 | } 31 | 32 | const UdTimePicker = (props) => { 33 | 34 | const onChange = (value) => { 35 | props.onFieldChange({ id: props.id, value }); 36 | props.setState({ value }); 37 | if (props.onChange) { 38 | props.onChange(value); 39 | } 40 | } 41 | 42 | var value = props.value; 43 | if (value === "") { 44 | value = null; 45 | } 46 | 47 | useEffect(() => { 48 | props.onFieldChange({ id: props.id, value: props.value }); 49 | return () => { } 50 | }, true) 51 | 52 | return ( 53 | 54 | onChange(value)} 58 | renderInput={(params) => } 59 | /> 60 | 61 | ) 62 | } 63 | 64 | export default withComponentFeatures(TimePickerWithContext); -------------------------------------------------------------------------------- /src/classes/Cmdlets/NewNivoChartAxisOptionsCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management.Automation; 3 | 4 | namespace UniversalDashboard.Enterprise 5 | { 6 | [Cmdlet(VerbsCommon.New, "UDNivoChartAxisOptions")] 7 | public class NewNivoChartAxisOptionsCommand : PSCmdlet 8 | { 9 | private readonly Dictionary _values = new Dictionary(); 10 | 11 | [Parameter] 12 | [ValidateSet("top", "right", "bottom", "left")] 13 | public string Position 14 | { 15 | set 16 | { 17 | _values.Add("orient", value); 18 | } 19 | } 20 | 21 | 22 | [Parameter] 23 | public int TickSize 24 | { 25 | set 26 | { 27 | _values.Add("tickSize", value); 28 | } 29 | } 30 | 31 | [Parameter] 32 | public int TickPadding 33 | { 34 | set 35 | { 36 | _values.Add("tickPadding", value); 37 | } 38 | } 39 | 40 | [Parameter] 41 | public int TickRotation 42 | { 43 | set 44 | { 45 | _values.Add("tickRotation", value); 46 | } 47 | } 48 | 49 | [Parameter] 50 | public string Legend 51 | { 52 | set 53 | { 54 | _values.Add("legend", value); 55 | } 56 | } 57 | 58 | [Parameter] 59 | [ValidateSet("center", "start", "end")] 60 | public string LegendPosition 61 | { 62 | set 63 | { 64 | _values.Add("legendPosition", value); 65 | } 66 | } 67 | 68 | [Parameter] 69 | public int LegendOffset 70 | { 71 | set 72 | { 73 | _values.Add("legendOffset", value); 74 | } 75 | } 76 | 77 | protected override void EndProcessing() { 78 | WriteObject(_values); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Components/code-editor.jsx: -------------------------------------------------------------------------------- 1 | import { KeyCode } from 'monaco-editor'; 2 | import React from 'react'; 3 | import MonacoEditor, { MonacoDiffEditor } from 'react-monaco-editor'; 4 | import ReactResizeDetector from 'react-resize-detector'; 5 | import { withComponentFeatures } from 'universal-dashboard' 6 | import { saveAs } from 'file-saver'; 7 | 8 | var editor; 9 | 10 | const UDMonacoEditor = props => { 11 | const onChange = (newValue) => { 12 | props.setState({ code: newValue }) 13 | } 14 | 15 | const editorDidMount = (editor, monaco) => { 16 | editor = editor; 17 | if (props.canSave) { 18 | editor.onKeyDown = (e) => { 19 | console.log(e); 20 | if (e.ctrlKey && e.keyCode === KeyCode.KEY_S) { 21 | e.preventDefault(); 22 | saveAs(new Blob([props.code], { type: 'text/plain' }), `${props.id}.${props.extension}`); 23 | } 24 | } 25 | } 26 | } 27 | 28 | const onResize = () => { 29 | if (editor) { 30 | editor.layout(); 31 | } 32 | } 33 | 34 | var control = props.original ? 35 | 36 | : 47 | 48 | ; 58 | 59 | if (props.autosize) { 60 | return ( 61 | 62 | <> 63 | {control} 64 | 65 | 66 | ) 67 | } 68 | 69 | return ( 70 | <> 71 | {control} 72 | 73 | ); 74 | } 75 | 76 | export default withComponentFeatures(UDMonacoEditor) -------------------------------------------------------------------------------- /src/Scripts/paper.ps1: -------------------------------------------------------------------------------- 1 | function New-UDPaper { 2 | <# 3 | .SYNOPSIS 4 | Creates a paper. 5 | 6 | .DESCRIPTION 7 | Creates a paper. Paper is used to highlight content within a page. 8 | 9 | .PARAMETER Id 10 | The ID of the component. It defaults to a random GUID. 11 | 12 | .PARAMETER Children 13 | The content of this paper element. 14 | 15 | .PARAMETER Width 16 | The width of this paper. 17 | 18 | .PARAMETER Height 19 | The height of this paper. 20 | 21 | .PARAMETER Square 22 | Whether this paper is square. 23 | 24 | .PARAMETER Style 25 | The CSS style to apply to this paper. 26 | 27 | .PARAMETER Elevation 28 | The elevation of this paper. 29 | 30 | .EXAMPLE 31 | Creates paper with a heading, custom style and an elevation of 4. 32 | 33 | New-UDPaper -Children { 34 | New-UDHeading -Text "hi" -Id 'hi' 35 | } -Style @{ 36 | backgroundColor = '#90caf9' 37 | } -Id 'paperElevation' -Elevation 4 38 | 39 | .NOTES 40 | General notes 41 | #> 42 | param( 43 | [Parameter()][string]$Id = ([Guid]::NewGuid()).ToString(), 44 | [Parameter()][Alias("Content")][ScriptBlock]$Children, 45 | [Parameter()][string]$Width = '500', 46 | [Parameter()][string]$Height = '500', 47 | [Parameter()][Switch]$Square, 48 | [Parameter()][Hashtable]$Style, 49 | [Parameter()][int]$Elevation, 50 | [Parameter()] 51 | [string]$ClassName 52 | ) 53 | 54 | End { 55 | $c = New-UDErrorBoundary -Content $Children 56 | 57 | @{ 58 | type = 'mu-paper' 59 | isPlugin = $true 60 | assetId = $MUAssetId 61 | 62 | id = $Id 63 | width = $Width 64 | children = $c 65 | height = $Height 66 | square = $Square.IsPresent 67 | style = $Style 68 | elevation = $Elevation 69 | className = $ClassName 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /examples/input-dynamic-pages.ps1: -------------------------------------------------------------------------------- 1 | $Page = New-UDPage -Url "/script/:id" -Endpoint { 2 | param($id) 3 | 4 | $PS1File = Invoke-WebRequest -Uri https://raw.githubusercontent.com/PoshCode/poshcode.github.io/data/scripts/$id.ps1 5 | 6 | New-UDCard -Title "Script $Id" -Text $PS1File.Content -Language powershell -Links @( 7 | New-UDLink -Text "Visit on GitHub" -Url "https://github.com/PoshCode/poshcode.github.io/blob/data/scripts/$Id.ps1" 8 | ) 9 | } 10 | 11 | $entry = New-UDPage -Url "/entry/:id" -Endpoint { 12 | param($id) 13 | 14 | $PS1File = Invoke-WebRequest -Uri https://raw.githubusercontent.com/PoshCode/poshcode.github.io/data/scripts/$id.md 15 | 16 | New-UDCard -Title "Script $Id" -Text $PS1File.Content -Language powershell -Links @( 17 | New-UDLink -Text "Visit on GitHub" -Url "https://github.com/PoshCode/poshcode.github.io/blob/data/scripts/$Id.md" 18 | New-UDLink -Text "View Raw Script" -Url "/script/$Id" 19 | ) 20 | } 21 | 22 | $HomePage = New-UDPage -Name "Home" -Content { 23 | New-UDCard -Title "About PoshCode.org Archive" -Text "This dashboard allows you to enter a PoshCode.org document ID and view the contents of it. Enter a value between 1000 and 4198." -Links @( 24 | New-UDLink -Text "Visit on GitHub" -Url "https://github.com/PoshCode/poshcode.github.io/" 25 | 26 | ) 27 | New-UDInput -Title "PoshCode.org Archive" -Endpoint { 28 | param($Id, [bool]$JustShowScript) 29 | 30 | if ($Id -lt 1000 -or $Id -gt 4198) { 31 | New-UDInputAction -Toast "$Id is not a valid PoshCode.org Archive Id!" 32 | } else { 33 | if ($JustShowScript) { 34 | New-UDInputAction -RedirectUrl "/script/$Id" 35 | } 36 | else { 37 | New-UDInputAction -RedirectUrl "/entry/$Id" 38 | } 39 | } 40 | } 41 | } 42 | 43 | $Dashboard = New-UDDashboard -Title "PoshCode.org Archive" -Pages @( 44 | $HomePage, 45 | $entry 46 | $Page 47 | ) 48 | 49 | Start-UDDashboard -Dashboard $Dashboard -Port "10000" 50 | Start-Process http://localhost:10000 51 | -------------------------------------------------------------------------------- /src/app/basics/lazy-element.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {getApiPath, getDashboardId } from './../config.jsx' 3 | import renderComponent from './../services/render-service.jsx'; 4 | 5 | export default class LazyElement extends React.Component { 6 | constructor() { 7 | super(); 8 | 9 | this.state = { 10 | loading: true, 11 | error: "" 12 | } 13 | } 14 | componentWillMount() { 15 | 16 | if (this.props.component.type === 'error') 17 | { 18 | return; 19 | } 20 | 21 | const dashboardId = getDashboardId(); 22 | 23 | var script = document.createElement('script'); 24 | script.onload = function() { 25 | this.setState({loading:false}); 26 | }.bind(this) 27 | script.src = getApiPath() + `/api/internal/javascript/${this.props.component.assetId}?dashboardId=${dashboardId}`; 28 | document.head.appendChild(script); 29 | } 30 | 31 | componentDidCatch(e) { 32 | this.setState({ 33 | error: e 34 | }) 35 | } 36 | 37 | render() { 38 | if (this.state.loading) { 39 | return
; 40 | } 41 | 42 | if (this.props.component.type === 'error') 43 | { 44 | return
{this.props.component.message}
45 | } 46 | 47 | if (this.state.error !== "") { 48 | return renderComponent({ 49 | type: 'error', 50 | errorRecords: [ 51 | { 52 | message: `There was an error rendering component of type ${this.props.component.type}. ${this.state.error}` 53 | } 54 | ] 55 | }); 56 | } 57 | 58 | var element = renderComponent(this.props.component, this.props.history, true); 59 | 60 | if (element == null) { 61 | return renderComponent({ 62 | type: 'error', 63 | message: `Component not registered: ${this.props.component.type}` 64 | }); 65 | } 66 | 67 | return element; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Components/table/components/baseTableHead.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TableCell, TableHead, TableRow, TableSortLabel } from "@mui/material"; 3 | import { setCellPadding } from "../v2/utilities"; 4 | 5 | export default function BaseTableHead({ headerGroups, columns, expandable }) { 6 | 7 | 8 | function setTableSortLabel(column) { 9 | return column.id !== "selection" && column.getSortByToggleProps ? ( 10 | 14 | ) : null; 15 | } 16 | 17 | return ( 18 | 19 | {headerGroups.map((headerGroup) => ( 20 | 21 | 22 | {expandable && } 23 | {headerGroup.headers.filter((column, index) => !column.hidden).map((column, index) => ( 24 | 34 | {column.render("Header")} 35 | {setTableSortLabel(column)} 36 | 37 | ))} 38 | 39 | column.showFilter) && "none", 42 | }} 43 | > 44 | {expandable && } 45 | {headerGroup.headers.filter((column, index) => !column.hidden).map((column, index) => ( 46 | 47 | {column.showFilter ? column.render("Filter") : null} 48 | 49 | ))} 50 | 51 | 52 | ))} 53 | 54 | ); 55 | } -------------------------------------------------------------------------------- /src/Scripts/timeline.ps1: -------------------------------------------------------------------------------- 1 | function New-UDTimeline { 2 | param( 3 | [Parameter()] 4 | [string]$Id = ([Guid]::NewGuid()).ToString(), 5 | [Parameter()] 6 | [Alias("Content")] 7 | [scriptblock]$Children, 8 | [Parameter()] 9 | [ValidateSet("right", 'left', 'alternate')] 10 | [string]$Position = "right" 11 | ) 12 | 13 | @{ 14 | isPlugin = $true 15 | assetId = $MUAssetId 16 | type = "mu-timeline" 17 | id = $id 18 | 19 | children = & $Children 20 | position = $Position.ToLower() 21 | } 22 | } 23 | 24 | function New-UDTimelineItem { 25 | param( 26 | [Parameter()] 27 | [ScriptBlock]$Content = {}, 28 | [Parameter()] 29 | [ScriptBlock]$OppositeContent = {}, 30 | [Parameter()] 31 | [Hashtable]$Icon, 32 | [Parameter()] 33 | [ValidateSet("error", 'grey', 'info', 'inherit', 'primary', 'secondary', 'success', 'warning')] 34 | [string]$Color = 'grey', 35 | [Parameter()] 36 | [ValidateSet('filled', 'outlined')] 37 | [string]$Variant = 'filled' 38 | ) 39 | 40 | @{ 41 | content = & $Content 42 | oppositeContent = & $OppositeContent 43 | icon = $icon 44 | color = $Color.ToLower() 45 | variant = $Variant.ToLower() 46 | } 47 | } 48 | 49 | <# 50 | 51 | New-UDTimeline -Children { 52 | New-UDTimelineItem -Content { 53 | 'Breakfast' 54 | } -OppositeContent { 55 | '7:45 AM' 56 | } 57 | New-UDTimelineItem -Content { 58 | 'Welcome Message' 59 | } -OppositeContent { 60 | '9:00 AM' 61 | } 62 | New-UDTimelineItem -Content { 63 | 'State of the Shell' 64 | } -OppositeContent { 65 | '9:30 AM' 66 | } 67 | New-UDTimelineItem -Content { 68 | 'General Session' 69 | } -OppositeContent { 70 | '11:00 AM' 71 | } 72 | } 73 | 74 | #> -------------------------------------------------------------------------------- /src/Components/menu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withComponentFeatures } from 'universal-dashboard'; 3 | import Button from '@mui/material/Button'; 4 | import Menu from '@mui/material/Menu'; 5 | import MenuItem from '@mui/material/MenuItem'; 6 | import Typography from '@mui/material/Typography'; 7 | import UDIcon from './icon'; 8 | 9 | function UDMenu(props) { 10 | const [anchorEl, setAnchorEl] = React.useState(null); 11 | 12 | const onChange = (value) => { 13 | setAnchorEl(null); 14 | props.setState({ value }); 15 | 16 | if (props.onChange) { 17 | props.onChange(value) 18 | } 19 | } 20 | 21 | const handleClick = (event) => { 22 | setAnchorEl(event.currentTarget); 23 | }; 24 | 25 | const handleClose = () => { 26 | setAnchorEl(null); 27 | }; 28 | 29 | let children = props.children; 30 | if (!children) { children = [] } 31 | if (!Array.isArray(children)) { children = [children] } 32 | 33 | let icon; 34 | if (props.icon) { 35 | icon = 36 | } 37 | 38 | return ( 39 |
40 | 44 | 51 | {children.map(child => )} 52 | 53 |
54 | ); 55 | } 56 | 57 | function UDMenuItem(props) { 58 | let icon; 59 | if (props.icon) { 60 | icon = 61 | } 62 | 63 | return ( 64 | props.onClick(props.value)}> 65 | {icon} 66 | {props.text} 67 | 68 | ) 69 | } 70 | 71 | export default withComponentFeatures(UDMenu); 72 | 73 | -------------------------------------------------------------------------------- /src/Scripts/datetime.ps1: -------------------------------------------------------------------------------- 1 | function New-UDDateTime { 2 | <# 3 | .SYNOPSIS 4 | This date and time component is used for formatting dates and times using the user's browser settings. 5 | 6 | .DESCRIPTION 7 | This date and time component is used for formatting dates and times using the user's browser settings. Since Universal Dashboard PowerShell scripts run within the server, the date and time settings of the user's system are not taken into account. This component formats date and time within the client's browser to take into account their locale and time zone. 8 | 9 | .PARAMETER Id 10 | The ID of this component. 11 | 12 | .PARAMETER InputObject 13 | The date and time object to format. 14 | 15 | .PARAMETER Format 16 | The format of the date and time. This component uses Day.JS. You can learn more about formatting options on their documentation: https://day.js.org/docs/en/display/format 17 | 18 | .PARAMETER LocalizedFormat 19 | The localized format for the date and time. Use this format if you would like to take the user's browser locale and time zone settings into account. 20 | 21 | .EXAMPLE 22 | Formats a date and time using the format 'DD/MM/YYYY' 23 | 24 | New-UDDateTime -InputObject (Get-Date) -Format 'DD/MM/YYYY' 25 | #> 26 | [CmdletBinding(DefaultParameterSetName = "LocalizedFormat")] 27 | param( 28 | [Parameter()] 29 | [string]$Id = [Guid]::NewGuid(), 30 | [Parameter(Mandatory, Position = 0)] 31 | [string]$InputObject, 32 | [Parameter(ParameterSetName = "Format")] 33 | [string]$Format = "DD/MM/YYYY", 34 | [Parameter(ParameterSetName = "LocalizedFormat")] 35 | [ValidateSet("LT", "LTS", "L", "LL", "LLL", "LLLL", "l", "ll", "lll", "llll")] 36 | [string]$LocalizedFormat = "LLL" 37 | ) 38 | 39 | $f = $Format 40 | if ($PSCmdlet.ParameterSetName -eq 'LocalizedFormat') 41 | { 42 | $f = $LocalizedFormat 43 | } 44 | 45 | @{ 46 | type = 'mu-datetime' 47 | id = $Id 48 | isPlugin = $true 49 | assetId = $MUAssetId 50 | 51 | inputObject = $InputObject 52 | format = $f 53 | } 54 | } -------------------------------------------------------------------------------- /src/Components/framework/ud-modal.jsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | 3 | import Dialog from '@mui/material/Dialog'; 4 | import DialogTitle from '@mui/material/DialogTitle'; 5 | import DialogContent from '@mui/material/DialogContent'; 6 | import DialogActions from '@mui/material/DialogActions'; 7 | 8 | export default function Modal(props) { 9 | 10 | const [openToken, setOpenToken] = useState(); 11 | const [closeToken, setCloseToken] = useState(); 12 | const [open, setOpen] = useState(false); 13 | const [modalOptions, setModalOptions] = useState({}); 14 | 15 | useEffect(() => { 16 | setOpenToken(UniversalDashboard.subscribe('modal.open', (x, props) => { 17 | setModalOptions(props); 18 | setOpen(true); 19 | })); 20 | 21 | setCloseToken(UniversalDashboard.subscribe('modal.close', () => { 22 | setOpen(false); 23 | setModalOptions({}); 24 | })); 25 | 26 | return () => { 27 | UniversalDashboard.unsubscribe(openToken); 28 | UniversalDashboard.unsubscribe(closeToken); 29 | } 30 | }, [true]); 31 | 32 | var header = null; 33 | if (modalOptions.header != null) { 34 | header = setOpen(false)}> 35 | {UniversalDashboard.renderComponent(modalOptions.header)} 36 | 37 | } 38 | 39 | var content = null; 40 | if (modalOptions.content != null) { 41 | content = 42 | {UniversalDashboard.renderComponent(modalOptions.content)} 43 | 44 | } 45 | 46 | var footer = null; 47 | if (modalOptions.footer != null) { 48 | footer = {UniversalDashboard.renderComponent(modalOptions.footer)} 49 | } 50 | 51 | return ( 52 | modalOptions.dismissible && setOpen(false)} maxWidth={modalOptions.maxWidth} fullScreen={modalOptions.fullScreen} fullWidth={modalOptions.fullWidth}> 53 | {header} 54 | {content} 55 | {footer} 56 | 57 | ) 58 | } -------------------------------------------------------------------------------- /examples/tree-view/filesystem.ps1: -------------------------------------------------------------------------------- 1 | $Dashboard = New-UdDashboard -Title "Tree View" -Content { 2 | New-UDRow -Columns { 3 | New-UDColumn -Size 3 -Content { 4 | $Root = New-UDTreeNode -Name 'FileSystem' -Id 'FileSystem' 5 | New-UDTreeView -ActiveBackgroundColor '#DFE8E4' -Node $Root -OnNodeClicked { 6 | param($Body) 7 | 8 | $Depth = [int]$Obj.NodeId 9 | 10 | if ($Obj.NodeId -eq 'FileSystem') { 11 | Get-PSDrive -PSProvider FileSystem | ForEach-Object { 12 | New-UDTreeNode -Name $_.Root -Id $_.Root -Icon hdd_o 13 | } 14 | } else { 15 | 16 | Get-ChildItem -Path $Obj.NodeId | ForEach-Object { 17 | if ($Obj.NodeId -eq $_.FullName) { 18 | return; 19 | } 20 | 21 | if ($_.PSIsContainer) { 22 | New-UDTreeNode -Name $_.Name -Id $_.FullName -Icon folder 23 | } else { 24 | New-UDTreeNode -Name $_.Name -Id $_.FullName -Icon file_text 25 | } 26 | 27 | } | ConvertTo-JsonEx 28 | 29 | Set-UDElement -Id "properties" -Content { 30 | New-UDGrid -Title $Obj.NodeId -Headers @("Name", "Value") -Properties @("Name", "Value") -Endpoint { 31 | $Obj = $ArgumentList[0] 32 | (Get-ItemProperty -Path $Obj.NodeId).PSObject.Properties | ForEach-Object { 33 | [PSCustomObject]@{ 34 | Name = $_.Name 35 | Value = if ( $_.Value -eq $null) { "" } else { $_.Value.ToString() } 36 | } 37 | } | Out-UDGridData 38 | } -ArgumentList $Obj 39 | } 40 | } 41 | } 42 | } 43 | New-UDColumn -Size 9 -Content { 44 | New-UDElement -Tag "div" -Id "properties" -Content {} 45 | } 46 | } 47 | 48 | 49 | } 50 | 51 | $Server = Start-UDDashboard -Port 10001 -Dashboard $dashboard -------------------------------------------------------------------------------- /docs/working-on-a-component.md: -------------------------------------------------------------------------------- 1 | # Working on a Component 2 | 3 | This document is intended to help you add a feature or fix a bug in a component. 4 | 5 | ## Step 1: Build Universal Dashboard 6 | 7 | Run the build script in VS Code using the 'Build Debug' task or by running the build script manually. 8 | 9 | ``` 10 | .\src\build.ps1 -Configuration Debug -NoHelp 11 | ``` 12 | 13 | You won't have to do this every time you want to edit a component. 14 | 15 | ## Step 2: Start the Materialize Webpack Dev Server 16 | 17 | You can start the Materialize Webpack Dev Server by running the 'Start Materialize Webpack Dev Server' task in VS Code or by running npm directly. 18 | 19 | ``` 20 | cd .\src\UniversalDashboard.Materialize 21 | npm run dev 22 | ``` 23 | 24 | ## Step 3: Modify the component 25 | 26 | The component code is in the `Components` folder and the PowerShell script is in the `Scripts` folder for the components. 27 | 28 | ``` 29 | cd .\src\UniversalDashboard.Materialize\Components 30 | cd .\src\UniversalDashboard.Materialize\Scripts 31 | ``` 32 | 33 | ## Step 4: Validate the Component 34 | 35 | You should write a test case that reproduces the bug or uses the new feature in the `.\src\UniversalDashboard.Materialize\Tests` folder. Once you have the test stubbed out, you can then run the test. 36 | 37 | An example test stub would look like this. 38 | 39 | ``` 40 | Context "Floating" { 41 | Set-TestDashboard { 42 | New-UDButton -Text "Click Me" -Id "button" -Floating 43 | } 44 | 45 | It "is floating" { 46 | 47 | } 48 | } 49 | ``` 50 | 51 | When you run the test, the browser will open and the button will quickly be shown. If you wish to leave the browser open so that you can work on your test or component, use `Wait-Debugger` to pause the test. 52 | 53 | ``` 54 | Context "Floating" { 55 | Set-TestDashboard { 56 | New-UDButton -Text "Click Me" -Id "button" -Floating 57 | } 58 | 59 | It "is floating" { 60 | Wait-Debugger 61 | } 62 | } 63 | ``` 64 | 65 | You can now edit the JSX file for the component and the webpack dev server will automatically recompile the component. 66 | 67 | If you edit the PowerShell script, you will have to restart your test script. 68 | -------------------------------------------------------------------------------- /src/Scripts/progress.ps1: -------------------------------------------------------------------------------- 1 | function New-UDProgress { 2 | <# 3 | .SYNOPSIS 4 | Creates a progress dialog. 5 | 6 | .DESCRIPTION 7 | Creates a progress dialog. Progress dialogs can show both determinate and indeterminate progress. They can also be circular or linear. 8 | 9 | .PARAMETER Id 10 | The ID of the component. It defaults to a random GUID. 11 | 12 | .PARAMETER PercentComplete 13 | The percent complete for the progress. 14 | 15 | .PARAMETER BackgroundColor 16 | The background color. 17 | 18 | .PARAMETER ProgressColor 19 | The progress bar color. 20 | 21 | .PARAMETER Circular 22 | Whether the progress is circular. 23 | 24 | .PARAMETER Size 25 | The size of the progress. 26 | 27 | .EXAMPLE 28 | Creates a progress bar at 75%. 29 | 30 | New-UDProgress -PercentComplete 75 31 | #> 32 | [CmdletBinding(DefaultParameterSetName = "indeterminate")] 33 | param( 34 | [Parameter()] 35 | [string]$Id = [Guid]::NewGuid().ToString(), 36 | [Parameter(ParameterSetName = "determinate")] 37 | [ValidateRange(0, 100)] 38 | $PercentComplete, 39 | [Parameter(ParameterSetName = "indeterminate")] 40 | [Parameter(ParameterSetName = "determinate")] 41 | [UniversalDashboard.Models.DashboardColor]$BackgroundColor, 42 | [Parameter()] 43 | [Alias("Color")] 44 | [UniversalDashboard.Models.DashboardColor]$ProgressColor, 45 | [Parameter(ParameterSetName = 'circular')] 46 | [Switch]$Circular, 47 | [Parameter(ParameterSetName = 'circular')] 48 | [ValidateSet('small', 'medium', 'large')] 49 | [string]$Size 50 | ) 51 | 52 | End { 53 | @{ 54 | id = $Id 55 | assetId = $MUAssetId 56 | isPlugin = $true 57 | type = "mu-progress" 58 | 59 | variant = $PSCmdlet.ParameterSetName 60 | percentComplete = $PercentComplete 61 | backgroundColor = $BackgroundColor.HtmlColor 62 | progressColor = $ProgressColor.HtmlColor 63 | circular = $Circular.IsPresent 64 | color = $Color 65 | size = $Size 66 | } 67 | } 68 | 69 | 70 | } -------------------------------------------------------------------------------- /src/Scripts/drawer.ps1: -------------------------------------------------------------------------------- 1 | function New-UDDrawer { 2 | <# 3 | .SYNOPSIS 4 | Creates a new drawer. 5 | 6 | .DESCRIPTION 7 | Creates a new drawer. A drawer is a navigational component that is typically used for navigating between pages. It can be used with New-UDAppBar to provide a custom nav bar. 8 | 9 | .PARAMETER Id 10 | The ID of the component. It defaults to a random GUID. 11 | 12 | .PARAMETER Children 13 | Navgiation controls to show within the drawer. Use New-UDList and New-UDListItem to generate links within the drawer. 14 | 15 | .PARAMETER Variant 16 | The type of drawer. Valid values include "persistent", "permanent", "temporary" 17 | 18 | .PARAMETER Anchor 19 | Where to anchor the drawer. Valid values incldue "left", "right", "top", "bottom" 20 | 21 | .EXAMPLE 22 | Creates a custom navbar using New-UDDrawer 23 | 24 | $Drawer = New-UDDrawer -Id 'drawer' -Children { 25 | New-UDList -Content { 26 | New-UDListItem -Id 'lstHome' -Label 'Home' -OnClick { 27 | Set-TestData 'Home' 28 | } -Content { 29 | New-UDListItem -Id 'lstNested' -Label 'Nested' -OnClick { 30 | Set-TestData 'Nested' 31 | } 32 | } 33 | } 34 | } 35 | 36 | New-UDElement -Tag 'main' -Content { 37 | New-UDAppBar -Children { New-UDTypography -Text 'Hello' -Paragraph } -Position relative -Drawer $Drawer 38 | } 39 | #> 40 | param( 41 | [Parameter()] 42 | [string]$Id = [Guid]::NewGuid(), 43 | [Parameter()] 44 | [Alias("Content")] 45 | [ScriptBlock]$Children, 46 | [ValidateSet("persistent", "permanent", "temporary")] 47 | [string]$Variant = "temporary", 48 | [ValidateSet("left", "right", "top", "bottom")] 49 | [string]$Anchor = "left", 50 | [Parameter()] 51 | [string]$ClassName 52 | ) 53 | 54 | try { 55 | $c = & $Children 56 | } 57 | catch { 58 | $c = New-UDError -Message $_ 59 | } 60 | 61 | @{ 62 | type = 'mu-drawer' 63 | id = $Id 64 | isPlugin = $true 65 | assetId = $MUAssetId 66 | children = $c 67 | variant = $Variant.ToLower() 68 | anchor = $Anchor.ToLower() 69 | className = $ClassName 70 | } 71 | } --------------------------------------------------------------------------------