From 6db21b0c43ca24d19a0f9a28a1ca61af0294e24a Mon Sep 17 00:00:00 2001 From: KeithALane <142168155+KeithALane@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:04:18 -0400 Subject: [PATCH 1/2] Update Pages.ps1 Modified the ConvertTo-PodeWebPage function by adding the -Group parameter to allow the page to be in a group when calling Add-PodeWebPage. --- src/Public/Pages.ps1 | 1149 +++++++++++++++++------------------------- 1 file changed, 467 insertions(+), 682 deletions(-) diff --git a/src/Public/Pages.ps1 b/src/Public/Pages.ps1 index 9376fb93..cb4bbca1 100644 --- a/src/Public/Pages.ps1 +++ b/src/Public/Pages.ps1 @@ -1,11 +1,8 @@ -function Set-PodeWebLoginPage { +function Set-PodeWebLoginPage +{ [CmdletBinding()] param( - [Parameter()] - [string] - $Id, - - [Parameter(Mandatory = $true)] + [Parameter(Mandatory=$true)] [string] $Authentication, @@ -14,10 +11,12 @@ function Set-PodeWebLoginPage { $Content, [Parameter()] + [Alias('Icon')] [string] $Logo, [Parameter()] + [Alias('IconUrl')] [string] $LogoUrl, @@ -49,26 +48,15 @@ function Set-PodeWebLoginPage { [string] $SignInMessage, - [Parameter()] - [string] - $LoginPath, - - [Parameter()] - [string] - $LogoutPath, - [switch] $PassThru ) # check content - if (!(Test-PodeWebContent -Content $Content -ComponentType Element)) { - throw 'The Login page can only contain other elements' + if (!(Test-PodeWebContent -Content $Content -ComponentType Layout, Element)) { + throw 'The Login page can only contain layouts and/or elements' } - # retrieve the auth from pode - $auth = Get-PodeAuth -Name $Authentication - # if no content, add default if (Test-PodeIsEmpty -Value $Content) { $Content = @( @@ -81,28 +69,31 @@ function Set-PodeWebLoginPage { Set-PodeWebState -Name 'auth' -Value $Authentication Set-PodeWebState -Name 'auth-props' -Value @{ Username = $UsernameProperty - Group = $GroupProperty - Avatar = $AvatarProperty - Theme = $ThemeProperty - Logout = $true + Group = $GroupProperty + Avatar = $AvatarProperty + Theme = $ThemeProperty + Logout = $true } - # get home url - $sysUrls = Get-PodeWebState -Name 'system-urls' - # set a default logo/url if ([string]::IsNullOrWhiteSpace($Logo)) { - $Logo = '/pode.web-static/images/icon.png' + $Logo = '/pode.web/images/icon.png' } $Logo = (Add-PodeWebAppPath -Url $Logo) - if (![string]::IsNullOrWhiteSpace($LogoUrl)) { - $LogoUrl = (Add-PodeWebAppPath -Url $LogoUrl) + if ([string]::IsNullOrWhiteSpace($LogoUrl)) { + $LogoUrl = '/' } + $LogoUrl = (Add-PodeWebAppPath -Url $LogoUrl) # background image $BackgroundImage = (Add-PodeWebAppPath -Url $BackgroundImage) + # set default failure/success urls + $auth = Get-PodeAuth -Name $Authentication + $auth.Failure.Url = (Add-PodeWebAppPath -Url '/login') + $auth.Success.Url = (Add-PodeWebAppPath -Url '/') + # is this auto-redirect oauth2? $isOAuth2 = ($auth.Scheme.Scheme -ieq 'oauth2') @@ -111,124 +102,91 @@ function Set-PodeWebLoginPage { $grantType = 'password' } - # generate page ID - $Id = Get-PodeWebPageId -Id $Id -Name 'login' -System - - # login / logout paths - if ([string]::IsNullOrEmpty($LoginPath)) { - $LoginPath = '/login' - } - - if ([string]::IsNullOrEmpty($LogoutPath)) { - $LogoutPath = '/logout' - } + # route path + $routePath = '/login' # setup page meta $pageMeta = @{ - Operation = 'New' - ComponentType = 'Page' - ObjectType = 'Page' - ID = $Id - Route = @{ - Login = @{ - Path = (Get-PodeWebPagePath -Name 'login' -Path $LoginPath -NoAppPath) - Url = (Get-PodeWebPagePath -Name 'login' -Path $LoginPath) - } - Logout = @{ - Path = (Get-PodeWebPagePath -Name 'logout' -Path $LogoutPath -NoAppPath) - Url = (Get-PodeWebPagePath -Name 'logout' -Path $LogoutPath) - } - } - Name = 'Login' - Content = $Content - SignInMessage = (Protect-PodeWebValue -Value $SignInMessage -Default 'Please sign in' -Encode) - Logo = @{ - IconUrl = $Logo - Url = $LogoUrl - } - BackgroundImage = $BackgroundImage - CopyRight = $Copyright - Authentication = $Authentication - IsOAuth2 = $isOAuth2 - GrantType = $grantType - IsSystem = $true - ResponseType = (Get-PodeWebResponseType) - } - - # set auth system urls - $sysUrls.Login = $pageMeta.Route.Login - $sysUrls.Logout = $pageMeta.Route.Logout - - # set default failure/success urls - if ([string]::IsNullOrWhiteSpace($auth.Failure.Url)) { - $auth.Failure.Url = $pageMeta.Route.Login.Url - } - - if ([string]::IsNullOrWhiteSpace($auth.Success.Url)) { - $auth.Success.Url = $sysUrls.Home.Url + ComponentType = 'Page' + ObjectType = 'Page' + Path = $routePath + Name = 'Login' + Content = $Content + SignInMessage = (Protect-PodeWebValue -Value $SignInMessage -Default 'Please sign in' -Encode) + IsSystem = $true } # add page meta to state $pages = Get-PodeWebState -Name 'pages' - $pages[$Id] = $pageMeta + $pages[$routePath] = $pageMeta # get the endpoints to bind $endpointNames = Get-PodeWebState -Name 'endpoint-name' # add the login route - Add-PodeRoute -Method Get -Path $pageMeta.Route.Login.Path -Authentication $Authentication -ArgumentList @{ ID = $Id } -EndpointName $endpointNames -Login -ScriptBlock { + Add-PodeRoute -Method Get -Path '/login' -Authentication $Authentication -ArgumentList @{ Path = $routePath } -EndpointName $endpointNames -Login -ScriptBlock { param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] Write-PodeWebViewResponse -Path 'login' -Data @{ - Page = $global:PageData - Theme = Get-PodeWebTheme - Sessions = @{ - Enabled = (Test-PodeSessionsEnabled) - Tabs = !(Test-PodeSessionScopeIsBrowser) + Page = $global:PageData + Content = $global:PageData.Content + Theme = Get-PodeWebTheme + Logo = $using:Logo + LogoUrl = $using:LogoUrl + Background = @{ + Image = $using:BackgroundImage } - Logo = $PageData.Logo.IconUrl - LogoUrl = $PageData.Logo.Url - Background = @{ - Image = $PageData.BackgroundImage - } - SignInMessage = $PageData.SignInMessage - Copyright = $PageData.Copyright - Auth = @{ - Name = $PageData.Authentication - IsOAuth2 = $PageData.IsOAuth2 - GrantType = $PageData.GrantType + SignInMessage = $global:PageData.SignInMessage + Copyright = $using:Copyright + Auth = @{ + Name = $using:Authentication + IsOAuth2 = $using:isOAuth2 + GrantType = $using:grantType } } $global:PageData = $null } - Add-PodeRoute -Method Post -Path $pageMeta.Route.Login.Path -Authentication $Authentication -EndpointName $endpointNames -Login + Add-PodeRoute -Method Post -Path '/login' -Authentication $Authentication -EndpointName $endpointNames -Login # add the logout route - Add-PodeRoute -Method Post -Path $pageMeta.Route.Logout.Path -Authentication $Authentication -EndpointName $endpointNames -Logout + Add-PodeRoute -Method Post -Path '/logout' -Authentication $Authentication -EndpointName $endpointNames -Logout - # login content - Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/content" -ArgumentList @{ ID = $Id } -EndpointName $endpointNames -ScriptBlock { - param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] - Write-PodeJsonResponse -Value $global:PageData.Content - $global:PageData = $null - } + # add an authenticated home route + Remove-PodeWebRoute -Method Get -Path '/' -EndpointName $endpointNames - # add sse open route - if (Test-PodeWebResponseType -Type Sse) { - Add-PodeRoute -Method Get -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-open" -ArgumentList @{ ID = $Id } -EndpointName $EndpointName -ScriptBlock { - param($Data) - ConvertTo-PodeSseConnection -Name 'Pode.Web.Actions' -Group $Data.ID + Add-PodeRoute -Method Get -Path '/' -Authentication $Authentication -EndpointName $endpointNames -ScriptBlock { + $page = Get-PodeWebFirstPublicPage + if ($null -ne $page) { + Move-PodeResponseUrl -Url (Get-PodeWebPagePath -Page $page) } - # add sse close route - Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-close" -EndpointName $EndpointName -ScriptBlock { - $clientId = Get-PodeHeader -Name 'X-PODE-SSE-CLIENT-ID' - if (![string]::IsNullOrEmpty($clientId)) { - Close-PodeSseConnection -Name 'Pode.Web.Actions' -ClientId $clientId + $authData = Get-PodeWebAuthData + $username = Get-PodeWebAuthUsername -AuthData $authData + $groups = Get-PodeWebAuthGroups -AuthData $authData + $avatar = Get-PodeWebAuthAvatarUrl -AuthData $authData + $theme = Get-PodeWebTheme + $navigation = Get-PodeWebNavDefault + + Write-PodeWebViewResponse -Path 'index' -Data @{ + Page = @{ + Name = 'Home' + Title = 'Home' + DisplayName = 'Home' + Path = '/' + IsSystem = $true + } + Theme = $theme + Navigation = $navigation + Auth = @{ + Enabled = $true + Logout = (Get-PodeWebState -Name 'auth-props').Logout + Authenticated = $authData.IsAuthenticated + Username = $username + Groups = $groups + Avatar = $avatar } } } @@ -238,32 +196,136 @@ function Set-PodeWebLoginPage { } } -function Add-PodeWebPage { +function Set-PodeWebHomePage +{ [CmdletBinding()] param( [Parameter()] - [string] - $Id, + [hashtable[]] + $Layouts, - [Parameter(Mandatory = $true)] + [Parameter()] [string] - $Name, + $DisplayName, [Parameter()] [string] - $Path, + $Title, [Parameter()] - [object[]] - $Middleware, + [hashtable[]] + $Navigation, [Parameter()] - [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] - $IfExists = 'Default', + [Alias('NoAuth')] + [switch] + $NoAuthentication, - [Parameter()] - [int] - $Index = [int]::MaxValue, + [switch] + $NoTitle, + + [switch] + $PassThru + ) + + # ensure layouts are correct + if (!(Test-PodeWebContent -Content $Layouts -ComponentType Layout)) { + throw 'The Home Page can only contain layouts' + } + + # set page title + if ([string]::IsNullOrWhiteSpace($DisplayName)) { + $DisplayName = 'Home' + } + + if ([string]::IsNullOrWhiteSpace($Title)) { + $Title = $DisplayName + } + + # route path + $routePath = '/' + + # setup page meta + $pageMeta = @{ + ComponentType = 'Page' + ObjectType = 'Page' + Path = $routePath + Name = 'Home' + Title = [System.Net.WebUtility]::HtmlEncode($Title) + DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) + NoTitle = $NoTitle.IsPresent + Navigation = $Navigation + Layouts = $Layouts + IsSystem = $true + } + + # add page meta to state + $pages = Get-PodeWebState -Name 'pages' + $pages[$routePath] = $pageMeta + + # does the page need auth? + $auth = $null + if (!$NoAuthentication) { + $auth = (Get-PodeWebState -Name 'auth') + } + + # get the endpoints to bind + $endpointNames = Get-PodeWebState -Name 'endpoint-name' + + # remove route + Remove-PodeWebRoute -Method Get -Path $routePath -EndpointName $endpointNames + + # re-add route + Add-PodeRoute -Method Get -Path $routePath -Authentication $auth -ArgumentList @{ Path = $routePath } -EndpointName $endpointNames -ScriptBlock { + param($Data) + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] + + # we either render the home page, or move to the first page if home page is blank + $comps = $global:PageData.Layouts + if (($null -eq $comps) -or ($comps.Length -eq 0)) { + $page = Get-PodeWebFirstPublicPage + if ($null -ne $page) { + Move-PodeResponseUrl -Url (Get-PodeWebPagePath -Page $page) + } + } + + $authData = Get-PodeWebAuthData + $username = Get-PodeWebAuthUsername -AuthData $authData + $groups = Get-PodeWebAuthGroups -AuthData $authData + $avatar = Get-PodeWebAuthAvatarUrl -AuthData $authData + $theme = Get-PodeWebTheme + $navigation = Get-PodeWebNavDefault -Items $global:PageData.Navigation + + Write-PodeWebViewResponse -Path 'index' -Data @{ + Page = $global:PageData + Theme = $theme + Navigation = $navigation + Layouts = $comps + Auth = @{ + Enabled = ![string]::IsNullOrWhiteSpace((Get-PodeWebState -Name 'auth')) + Logout = (Get-PodeWebState -Name 'auth-props').Logout + Authenticated = $authData.IsAuthenticated + Username = $username + Groups = $groups + Avatar = $avatar + } + } + + $global:PageData = $null + } + + if ($PassThru) { + return $pageMeta + } +} + +function Add-PodeWebPage +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, [Parameter()] [string] @@ -274,9 +336,8 @@ function Add-PodeWebPage { $Title, [Parameter()] - [ValidateNotNull()] [string] - $Group = [string]::Empty, + $Group, [Parameter()] [ValidateNotNullOrEmpty()] @@ -285,7 +346,7 @@ function Add-PodeWebPage { [Parameter()] [hashtable[]] - $Content, + $Layouts, [Parameter()] [scriptblock] @@ -338,31 +399,17 @@ function Add-PodeWebPage { [switch] $NoSidebar, - [switch] - $NoNavigation, - - [switch] - $HomePage, - [switch] $PassThru ) - # ensure elements are correct - if (!(Test-PodeWebContent -Content $Content -ComponentType Element)) { - throw 'A Page can only contain elements' - } - - # test if group exists - otherwise create a basic group entry - if (!(Test-PodeWebPageGroup -Name $Group)) { - New-PodeWebPageGroup -Name $Group + # ensure layouts are correct + if (!(Test-PodeWebContent -Content $Layouts -ComponentType Layout)) { + throw 'A Page can only contain layouts' } - # generate page ID - $Id = Get-PodeWebPageId -Id $Id -Name $Name -Group $Group - # test if page/page-link exists - if (Test-PodeWebPage -Id $Id) { + if (Test-PodeWebPage -Name $Name -Group $Group -NoGroup) { throw "Web page/link already exists: $($Name) [Group: $($Group)]" } @@ -375,259 +422,150 @@ function Add-PodeWebPage { $Title = $DisplayName } - # check for scoped vars - $ScriptBlock, $mainUsingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState - $HelpScriptBlock, $helpUsingVars = Convert-PodeScopedVariables -ScriptBlock $HelpScriptBlock -PSSession $PSCmdlet.SessionState + # build the route path + $routePath = (Get-PodeWebPagePath -Name $Name -Group $Group -NoAppPath) # setup page meta $pageMeta = @{ - Operation = 'New' - ComponentType = 'Page' - ObjectType = 'Page' - ID = $Id - Index = $Index - Group = $Group - Name = $Name - Title = [System.Net.WebUtility]::HtmlEncode($Title) - DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) - NoTitle = $NoTitle.IsPresent - NoBackArrow = $NoBackArrow.IsPresent - NoBreadcrumb = $NoBreadcrumb.IsPresent - NewTab = $NewTab.IsPresent - IsDynamic = $false - ShowHelp = ($null -ne $HelpScriptBlock) - Icon = $Icon - Path = (Get-PodeWebPagePath -Name $Name -Group $Group -Path $Path -NoAppPath) - Url = (Get-PodeWebPagePath -Name $Name -Group $Group -Path $Path) - Hide = $Hide.IsPresent - NoSidebar = $NoSidebar.IsPresent - NoNavigation = $NoNavigation.IsPresent - Navigation = $Navigation - Logic = @{ - ScriptBlock = $ScriptBlock - UsingVariables = $mainUsingVars - } - Help = @{ - ScriptBlock = $HelpScriptBlock - UsingVariables = $helpUsingVars - } - Content = $Content - Authentication = $null + ComponentType = 'Page' + ObjectType = 'Page' + Path = $routePath + Name = $Name + Title = [System.Net.WebUtility]::HtmlEncode($Title) + DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) + NoTitle = $NoTitle.IsPresent + NoBackArrow = $NoBackArrow.IsPresent + NoBreadcrumb = $NoBreadcrumb.IsPresent + NewTab = $NewTab.IsPresent + IsDynamic = $false + ShowHelp = ($null -ne $HelpScriptBlock) + Icon = $Icon + Group = $Group + Url = (Get-PodeWebPagePath -Name $Name -Group $Group) + Hide = $Hide.IsPresent + NoSidebar = $NoSidebar.IsPresent + Navigation = $Navigation + ScriptBlock = $ScriptBlock + HelpScriptBlock = $HelpScriptBlock + Layouts = $Layouts NoAuthentication = $NoAuthentication.IsPresent - IsHomePage = $HomePage.IsPresent - Access = @{ + Access = @{ Groups = @($AccessGroups) - Users = @($AccessUsers) + Users = @($AccessUsers) } - ResponseType = (Get-PodeWebResponseType) } + # add page meta to state + $pages = Get-PodeWebState -Name 'pages' + $pages[$routePath] = $pageMeta + # does the page need auth? $auth = $null - if (!$pageMeta.NoAuthentication) { - $auth = Get-PodeWebState -Name 'auth' + if (!$NoAuthentication) { + $auth = (Get-PodeWebState -Name 'auth') } - $pageMeta.Authentication = $auth - - # add page meta to state - Register-PodeWebPage -Metadata $pageMeta # get the endpoints to bind if (Test-PodeIsEmpty $EndpointName) { $EndpointName = Get-PodeWebState -Name 'endpoint-name' } - # remove the "root" page, if "root-redirect" was originally flagged and this page is for the root path - if (($pageMeta.Path -eq '/') -and (Get-PodeWebState -Name 'root-redirect')) { - Remove-PodeRoute -Method Get -Path '/' - } - # add the page route - Add-PodeRoute -Method Get -Path $pageMeta.Path -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -Middleware $Middleware -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { + Add-PodeRoute -Method Get -Path $routePath -Authentication $auth -ArgumentList @{ Data = $ArgumentList; Path = $routePath } -EndpointName $EndpointName -ScriptBlock { param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] - - # get auth details of a user - $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) - $authMeta = $null - - if ($authEnabled) { - $authData = Get-PodeAuthUser - $authMeta = @{ - Enabled = $true - Logout = (Get-PodeWebState -Name 'auth-props').Logout - Authenticated = ($null -ne $authData) - } + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] - if ($authMeta.Authenticated) { - $authMeta['Username'] = Get-PodeWebAuthUsername -User $authData - $authMeta['Groups'] = Get-PodeWebAuthGroups -User $authData - $authMeta['Avatar'] = Get-PodeWebAuthAvatarUrl -User $authData + if (!$global:PageData.NoBackArrow) { + $global:PageData.ShowBack = (($null -ne $WebEvent.Query) -and ($WebEvent.Query.Count -gt 0)) + if ($global:PageData.ShowBack -and ($WebEvent.Query.Count -eq 1) -and ($WebEvent.Query.ContainsKey(''))) { + $global:PageData.ShowBack = $false } } - - # check access - 403 if denied - if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { - Set-PodeResponseStatus -Code 403 - } - else { - # show a back arrow? - if (!$global:PageData.NoBackArrow) { - $global:PageData.ShowBack = (($null -ne $WebEvent.Query) -and ($WebEvent.Query.Count -gt 0)) - if ($global:PageData.ShowBack -and ($WebEvent.Query.Count -eq 1) -and ($WebEvent.Query.ContainsKey(''))) { - $global:PageData.ShowBack = $false - } - } - else { - $global:PageData.ShowBack = $false - } - - # render the page - Write-PodeWebViewResponse -Path 'index' -Data @{ - Page = $global:PageData - Title = $global:PageData.Title - DisplayName = $global:PageData.DisplayName - Theme = (Get-PodeWebTheme) - Sessions = @{ - Enabled = (Test-PodeSessionsEnabled) - Tabs = !(Test-PodeSessionScopeIsBrowser) - } - Auth = $authMeta - } + $global:PageData.ShowBack = $false } - $global:PageData = $null - } - - Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/content" -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { - param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] - Set-PodeWebMetadata - # get auth details of a user - $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) - $authMeta = $null - - if ($authEnabled) { - $authData = Get-PodeAuthUser - if ($null -ne $authData) { - $authMeta = @{ - Username = (Get-PodeWebAuthUsername -User $authData) - Groups = (Get-PodeWebAuthGroups -User $authData) - } - } + $authData = Get-PodeWebAuthData + $username = Get-PodeWebAuthUsername -AuthData $authData + $groups = Get-PodeWebAuthGroups -AuthData $authData + $avatar = Get-PodeWebAuthAvatarUrl -AuthData $authData + $theme = Get-PodeWebTheme + $navigation = Get-PodeWebNavDefault -Items $global:PageData.Navigation + + $authMeta = @{ + Enabled = ![string]::IsNullOrWhiteSpace((Get-PodeWebState -Name 'auth')) + Logout = (Get-PodeWebState -Name 'auth-props').Logout + Authenticated = $authData.IsAuthenticated + Username = $username + Groups = $groups + Avatar = $avatar } # check access - 403 if denied if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { Set-PodeResponseStatus -Code 403 } + else { - # if we have a scriptblock, invoke that to get dynamic elements - $content = $null - if ($null -ne $global:PageData.Logic.ScriptBlock) { - $content = Invoke-PodeWebScriptBlock -Logic $global:PageData.Logic -Arguments $Data.Data + # if we have a scriptblock, invoke that to get dynamic components + $layouts = $null + if ($null -ne $global:PageData.ScriptBlock) { + $layouts = Invoke-PodeScriptBlock -ScriptBlock $global:PageData.ScriptBlock -Arguments $Data.Data -Splat -Return } - if (($null -eq $content) -or ($content.Length -eq 0)) { - $content = $global:PageData.Content + if (($null -eq $layouts) -or ($layouts.Length -eq 0)) { + $layouts = $global:PageData.Layouts } - $navigation = Get-PodeWebNavDefault -Items $global:PageData.Navigation - Write-PodeJsonResponse -Value (@($navigation) + @($content)) - } - - $global:PageData = $null - } - - # add sse open route - if (Test-PodeWebResponseType -Type Sse) { - Add-PodeRoute -Method Get -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-open" -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { - param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + $breadcrumb = $null + $filteredLayouts = @() - # get auth details of a user - $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) - $authMeta = $null - - if ($authEnabled) { - $authData = Get-PodeAuthUser - if ($null -ne $authData) { - $authMeta = @{ - Username = (Get-PodeWebAuthUsername -User $authData) - Groups = (Get-PodeWebAuthGroups -User $authData) + foreach ($item in $layouts) { + if ($item.ObjectType -ieq 'breadcrumb') { + if ($null -ne $breadcrumb) { + throw "Cannot set two brecrumb trails on one page" } - } - } - - # check access - 403 if denied - if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { - Set-PodeResponseStatus -Code 403 - } - else { - # open new sse connection - ConvertTo-PodeSseConnection -Name 'Pode.Web.Actions' -Group $Data.ID - } - $global:PageData = $null - } - - # add sse close route - Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-close" -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { - param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] - - # get auth details of a user - $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) - $authMeta = $null - - if ($authEnabled) { - $authData = Get-PodeAuthUser - if ($null -ne $authData) { - $authMeta = @{ - Username = (Get-PodeWebAuthUsername -User $authData) - Groups = (Get-PodeWebAuthGroups -User $authData) - } + $breadcrumb = $item } - } - - # check access - 403 if denied - if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { - Set-PodeResponseStatus -Code 403 - } - else { - # if a connection in header, close connection - $clientId = Get-PodeHeader -Name 'X-PODE-SSE-CLIENT-ID' - if (![string]::IsNullOrEmpty($clientId)) { - Close-PodeSseConnection -Name 'Pode.Web.Actions' -ClientId $clientId + else { + $filteredLayouts += $item } } - $global:PageData = $null + Write-PodeWebViewResponse -Path 'index' -Data @{ + Page = $global:PageData + Title = $global:PageData.Title + DisplayName = $global:PageData.DisplayName + Theme = $theme + Navigation = $navigation + Breadcrumb = $breadcrumb + Layouts = $filteredLayouts + Auth = $authMeta + } } + + $global:PageData = $null } # add the page help route - $helpPath = "/pode.web-dynamic/pages/$($pageMeta.ID)/help" + $helpPath = "$($routePath)/help" if (($null -ne $HelpScriptBlock) -and !(Test-PodeWebRoute -Path $helpPath)) { - Add-PodeRoute -Method Post -Path $helpPath -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { + Add-PodeRoute -Method Post -Path $helpPath -Authentication $auth -ArgumentList @{ Data = $ArgumentList; Path = $routePath } -EndpointName $EndpointName -ScriptBlock { param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] - Set-PodeWebMetadata + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] # get auth details of a user - $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) - $authMeta = $null - - if ($authEnabled) { - $authData = Get-PodeAuthUser - if ($null -ne $authData) { - $authMeta = @{ - Username = (Get-PodeWebAuthUsername -User $authData) - Groups = (Get-PodeWebAuthGroups -User $authData) - } - } + $authData = Get-PodeWebAuthData + $username = Get-PodeWebAuthUsername -AuthData $authData + $groups = Get-PodeWebAuthGroups -AuthData $authData + + $authMeta = @{ + Enabled = ![string]::IsNullOrWhiteSpace((Get-PodeWebState -Name 'auth')) + Authenticated = $authData.IsAuthenticated + Username = $username + Groups = $groups } # check access - 403 if denied @@ -635,9 +573,12 @@ function Add-PodeWebPage { Set-PodeResponseStatus -Code 403 } else { - $result = Invoke-PodeWebScriptBlock -Logic $global:PageData.Help -Arguments $Data.Data + $result = Invoke-PodeScriptBlock -ScriptBlock $global:PageData.HelpScriptBlock -Arguments $Data.Data -Splat -Return + if ($null -eq $result) { + $result = @() + } - if (($null -ne $result) -and !$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { + if (!$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { Write-PodeJsonResponse -Value $result } } @@ -651,52 +592,36 @@ function Add-PodeWebPage { } } -function Add-PodeWebPageLink { - [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')] +function Add-PodeWebPageLink +{ + [CmdletBinding(DefaultParameterSetName='ScriptBlock')] param( - [Parameter()] - [string] - $Id, - - [Parameter(Mandatory = $true)] + [Parameter(Mandatory=$true)] [string] $Name, - [Parameter(ParameterSetName = 'ScriptBlock')] - [object[]] - $Middleware, - - [Parameter()] - [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] - $IfExists = 'Default', - - [Parameter()] - [int] - $Index = [int]::MaxValue, - [Parameter()] [string] $DisplayName, [Parameter()] - [ValidateNotNull()] [string] - $Group = [string]::Empty, + $Group, [Parameter()] [ValidateNotNullOrEmpty()] [string] $Icon = 'file', - [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')] + [Parameter(Mandatory=$true, ParameterSetName='ScriptBlock')] [scriptblock] $ScriptBlock, - [Parameter(ParameterSetName = 'ScriptBlock')] + [Parameter(ParameterSetName='ScriptBlock')] [object[]] $ArgumentList, - [Parameter(Mandatory = $true, ParameterSetName = 'Url')] + [Parameter(Mandatory=$true, ParameterSetName='Url')] [string] $Url, @@ -708,16 +633,16 @@ function Add-PodeWebPageLink { [string[]] $AccessUsers = @(), - [Parameter(ParameterSetName = 'ScriptBlock')] + [Parameter(ParameterSetName='ScriptBlock')] [string[]] $EndpointName, - [Parameter(ParameterSetName = 'ScriptBlock')] + [Parameter(ParameterSetName='ScriptBlock')] [Alias('NoAuth')] [switch] $NoAuthentication, - [Parameter(ParameterSetName = 'Url')] + [Parameter(ParameterSetName='Url')] [switch] $NewTab, @@ -725,16 +650,8 @@ function Add-PodeWebPageLink { $Hide ) - # test if group exists - otherwise create a basic group entry - if (!(Test-PodeWebPageGroup -Name $Group)) { - New-PodeWebPageGroup -Name $Group - } - - # generate page ID - $Id = Get-PodeWebPageId -Id $Id -Name $Name -Group $Group - # test if page/page-link exists - if (Test-PodeWebPage -Id $Id) { + if (Test-PodeWebPage -Name $Name -Group $Group -NoGroup) { throw "Web page/link already exists: $($Name) [Group: $($Group)]" } @@ -743,78 +660,65 @@ function Add-PodeWebPageLink { $DisplayName = $Name } - # check for scoped vars - $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState - # setup page meta $pageMeta = @{ - Operation = 'New' - ComponentType = 'Page' - ObjectType = 'Link' - ID = $Id - Index = $Index - Name = $Name - Group = $Group - DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) - NewTab = $NewTab.IsPresent - Icon = $Icon - Path = (Get-PodeWebPagePath -Name $Name -Group $Group -NoAppPath) - Url = (Add-PodeWebAppPath -Url $Url) - Hide = $Hide.IsPresent - IsDynamic = ($null -ne $ScriptBlock) - Logic = @{ - ScriptBlock = $ScriptBlock - UsingVariables = $usingVars - } - Authentication = $null - NoAuthentication = $NoAuthentication.IsPresent - Access = @{ + ComponentType = 'Page' + ObjectType = 'Link' + Name = $Name + DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) + NewTab = $NewTab.IsPresent + Icon = $Icon + Group = $Group + Url = (Add-PodeWebAppPath -Url $Url) + Hide = $Hide.IsPresent + IsDynamic = ($null -ne $ScriptBlock) + ScriptBlock = $ScriptBlock + Access = @{ Groups = @($AccessGroups) - Users = @($AccessUsers) + Users = @($AccessUsers) } - NoEvents = $true + NoEvents = $true } - # does the page need auth? - $auth = [string]::Empty - if (!$pageMeta.NoAuthentication) { - $auth = Get-PodeWebState -Name 'auth' - } - $pageMeta.Authentication = $auth + # build the route path + $routePath = (Get-PodeWebPagePath -Name $Name -Group $Group -NoAppPath) # add page meta to state - Register-PodeWebPage -Metadata $pageMeta + $pages = Get-PodeWebState -Name 'pages' + $pages[$routePath] = $pageMeta # add page link - if (($null -ne $ScriptBlock) -and !(Test-PodeWebRoute -Path $pageMeta.Path)) { - if (Test-PodeIsEmpty $EndpointName) { - $EndpointName = Get-PodeWebState -Name 'endpoint-name' + if (($null -ne $ScriptBlock) -and !(Test-PodeWebRoute -Path $routePath)) { + $auth = $null + if (!$NoAuthentication) { + $auth = (Get-PodeWebState -Name 'auth') } - # remove the "root" page, if "root-redirect" was originally flagged and this page is for the root path - if (($pageMeta.Path -eq '/') -and (Get-PodeWebState -Name 'root-redirect')) { - Remove-PodeRoute -Method Get -Path '/' + if (Test-PodeIsEmpty $EndpointName) { + $EndpointName = Get-PodeWebState -Name 'endpoint-name' } - # add the route - Add-PodeRoute -Method Post -Path $pageMeta.Path -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -Middleware $Middleware -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { + Add-PodeRoute -Method Post -Path $routePath -Authentication $auth -ArgumentList @{ Data = $ArgumentList; Path = $routePath } -EndpointName $EndpointName -ScriptBlock { param($Data) - $pageData = (Get-PodeWebState -Name 'pages')[$Data.ID] - Set-PodeWebMetadata + $pageData = (Get-PodeWebState -Name 'pages')[$Data.Path] - $result = Invoke-PodeWebScriptBlock -Logic $pageData.Logic -Arguments $Data.Data + $result = Invoke-PodeScriptBlock -ScriptBlock $pageData.ScriptBlock -Arguments $Data.Data -Splat -Return + if ($null -eq $result) { + $result = @() + } - if (($null -ne $result) -and !$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { + if (!$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { Write-PodeJsonResponse -Value $result } } } } -function ConvertTo-PodeWebPage { +function ConvertTo-PodeWebPage +{ [CmdletBinding()] param( - [Parameter(ValueFromPipeline = $true)] + [Parameter(ValueFromPipeline=$true)] [string[]] $Commands, @@ -822,6 +726,10 @@ function ConvertTo-PodeWebPage { [string] $Module, + [Parameter()] #Added by Keith 09/22/2025 to allow group names to be used with ConvertTo-PodeWebPage + [string] + $Group, + [switch] $GroupVerbs, @@ -836,7 +744,7 @@ function ConvertTo-PodeWebPage { Import-PodeModule -Name $Module Export-PodeModule -Name $Module - Write-Verbose 'Getting exported commands from module' + Write-Verbose "Getting exported commands from module" $ModuleCommands = (Get-Module -Name $Module | Sort-Object -Descending | Select-Object -First 1).ExportedCommands.Keys # if commands were supplied validate them - otherwise use all exported ones @@ -859,8 +767,20 @@ function ConvertTo-PodeWebPage { throw 'No commands supplied to convert to Pages' } - $sysParams = [System.Management.Automation.PSCmdlet]::CommonParameters.GetEnumerator() | Foreach-Object { $_ } - + $sysParams = @( + 'Verbose', + 'Debug', + 'ErrorAction', + 'WarningAction', + 'InformationAction', + 'ErrorVariable', + 'WarningVariable', + 'InformationVariable', + 'OutVariable', + 'OutBuffer', + 'PipelineVariable' + ) + # create the pages for each of the commands foreach ($cmd in $Commands) { Write-Verbose "Building page for $($cmd)" @@ -881,128 +801,112 @@ function ConvertTo-PodeWebPage { } $tabs = New-PodeWebTabs -Tabs @(foreach ($set in $sets) { - # build name - $name = $set.Name - if ([string]::IsNullOrWhiteSpace($name) -or ($set.Name -iin @('__AllParameterSets'))) { - $name = 'Default' + $elements = @(foreach ($param in $set.Parameters) { + if ($sysParams -icontains $param.Name) { + continue } - # build input controls - $elements = @(foreach ($param in $set.Parameters) { - if ($sysParams -icontains $param.Name) { - continue - } + $type = $param.ParameterType.Name - $type = $param.ParameterType.Name + $default = $null + if ($null -ne $paramDefs) { + $default = ($paramDefs | Where-Object { $_.DefaultValue -and $_.Name.Extent.Text -ieq "`$$($param.Name)" }).DefaultValue.Value + } - $default = $null - if ($null -ne $paramDefs) { - $default = ($paramDefs | Where-Object { $_.DefaultValue -and $_.Name.Extent.Text -ieq "`$$($param.Name)" }).DefaultValue.Value + if ($type -iin @('boolean', 'switchparameter')) { + New-PodeWebCheckbox -Name $param.Name -AsSwitch + } + else { + switch ($type) { + 'pscredential' { + New-PodeWebCredential -Name $param.Name } - if ($type -iin @('boolean', 'switchparameter')) { - New-PodeWebCheckbox -Name "$($name)_$($param.Name)" -DisplayName $param.Name -AsSwitch - } - else { - switch ($type) { - 'pscredential' { - New-PodeWebCredential -Name "$($name)_$($param.Name)" -DisplayName $param.Name - } - - default { - $multiple = $param.ParameterType.Name.EndsWith('[]') - - if ($param.Attributes.TypeId.Name -icontains 'ValidateSetAttribute') { - $values = ($param.Attributes | Where-Object { $_.TypeId.Name -ieq 'ValidateSetAttribute' }).ValidValues - New-PodeWebSelect -Name "$($name)_$($param.Name)" -DisplayName $param.Name -Options $values -SelectedValue $default -Multiple:$multiple - } - elseif ($param.ParameterType.BaseType.Name -ieq 'enum') { - $values = [enum]::GetValues($param.ParameterType) - New-PodeWebSelect -Name "$($name)_$($param.Name)" -DisplayName $param.Name -Options $values -SelectedValue $default -Multiple:$multiple - } - else { - New-PodeWebTextbox -Name "$($name)_$($param.Name)" -DisplayName $param.Name -Value $default - } - } + default { + $multiple = $param.ParameterType.Name.EndsWith('[]') + + if ($param.Attributes.TypeId.Name -icontains 'ValidateSetAttribute') { + $values = ($param.Attributes | Where-Object { $_.TypeId.Name -ieq 'ValidateSetAttribute' }).ValidValues + New-PodeWebSelect -Name $param.Name -Options $values -SelectedValue $default -Multiple:$multiple + } + elseif ($param.ParameterType.BaseType.Name -ieq 'enum') { + $values = [enum]::GetValues($param.ParameterType) + New-PodeWebSelect -Name $param.Name -Options $values -SelectedValue $default -Multiple:$multiple + } + else { + New-PodeWebTextbox -Name $param.Name -Value $default } } - }) + } + } + }) - $elements += (New-PodeWebHidden -Name '_Function_Name_' -Value $cmd) - $elements += (New-PodeWebHidden -Name '_Parameter_Set_Name_' -Value $name) + $elements += (New-PodeWebHidden -Name '_Function_Name_' -Value $cmd) - # build form - $formId = "form_param_$($cmd)_$($name)" - $form = New-PodeWebForm -Name "$($name)_Parameters_Form" -Id $formId -Content $elements -NoAuthentication:$NoAuthentication -ScriptBlock { - $cmd = $WebEvent.Data['_Function_Name_'] - $WebEvent.Data.Remove('_Function_Name_') + $name = $set.Name + if ([string]::IsNullOrWhiteSpace($name) -or ($set.Name -iin @('__AllParameterSets'))) { + $name = 'Default' + } - $setName = $WebEvent.Data['_Parameter_Set_Name_'] - $WebEvent.Data.Remove('_Parameter_Set_Name_') + $formId = "form_param_$($cmd)_$($name)" - $_args = @{} - foreach ($key in $WebEvent.Data.Keys) { - $argKey = $key -ireplace "$($setName)_", '' + $form = New-PodeWebForm -Name Parameters -Id $formId -Content $elements -AsCard -NoAuthentication:$NoAuthentication -ScriptBlock { + $cmd = $WebEvent.Data['_Function_Name_'] + $WebEvent.Data.Remove('_Function_Name_') - if ($argKey -imatch '(?.+)_(Username|Password)$') { - $name = $Matches['name'] - $uKey = "$($argKey)_$($name)_Username" - $pKey = "$($argKey)_$($name)_Password" + $_args = @{} + foreach ($key in $WebEvent.Data.Keys) { + if ($key -imatch '(?.+)_(Username|Password)$') { + $name = $Matches['name'] + $uKey = "$($name)_Username" + $pKey = "$($name)_Password" - if (![string]::IsNullOrWhiteSpace($WebEvent.Data[$uKey]) -and ![string]::IsNullOrWhiteSpace($WebEvent.Data[$pKey])) { - $creds = (New-Object System.Management.Automation.PSCredential -ArgumentList $WebEvent.Data[$uKey], (ConvertTo-SecureString -AsPlainText $WebEvent.Data[$pKey] -Force)) - $_args[$name] = $creds - } + if (![string]::IsNullOrWhiteSpace($WebEvent.Data[$uKey]) -and ![string]::IsNullOrWhiteSpace($WebEvent.Data[$pKey])) { + $creds = (New-Object System.Management.Automation.PSCredential -ArgumentList $WebEvent.Data[$uKey], (ConvertTo-SecureString -AsPlainText $WebEvent.Data[$pKey] -Force)) + $_args[$name] = $creds + } + } + else { + if ($WebEvent.Data[$key] -iin @('true', 'false')) { + $_args[$key] = ($WebEvent.Data[$key] -ieq 'true') } else { - if ($WebEvent.Data[$key] -iin @('true', 'false')) { - $_args[$argKey] = ($WebEvent.Data[$key] -ieq 'true') + if ($WebEvent.Data[$key].Contains(',')) { + $_args[$key] = ($WebEvent.Data[$key] -isplit ',' | ForEach-Object { $_.Trim() }) } else { - if ($WebEvent.Data[$key].Contains(',')) { - $_args[$argKey] = ($WebEvent.Data[$key] -isplit ',' | ForEach-Object { $_.Trim() }) - } - else { - $_args[$argKey] = $WebEvent.Data[$key] - } + $_args[$key] = $WebEvent.Data[$key] } } } + } - try { - (. $cmd @_args) | - New-PodeWebTextbox -Name 'Output_Result' -Multiline -Preformat | - Out-PodeWebElement - } - catch { - $_.Exception | - New-PodeWebTextbox -Name 'Output_Error' -Multiline -Preformat | - Out-PodeWebElement - } + try { + (. $cmd @_args) | Out-PodeWebTextbox -Multiline -Preformat + } + catch { + $_.Exception | Out-PodeWebTextbox -Multiline -Preformat } + } - $card = New-PodeWebCard -Name "$($name)_Parameters" -DisplayName 'Parameters' -Content $form - New-PodeWebTab -Name $name -Content $card - }) + New-PodeWebTab -Name $name -Layouts $form + }) + + if ($group -eq [string]::Empty) { #Added by Keith 09/22/2025 to allow group names to be used with ConvertTo-PodeWebPage - $group = [string]::Empty - if ($GroupVerbs) { - $group = $cmdInfo.Verb - if ([string]::IsNullOrWhiteSpace($group)) { - $group = '_' + if ($GroupVerbs) { + $group = $cmdInfo.Verb + if ([string]::IsNullOrWhiteSpace($group)) { + $group = '_' + } } } - - Add-PodeWebPage ` - -Name $cmd ` - -Icon Settings ` - -Content $tabs ` - -Group $group ` - -NoAuthentication:$NoAuthentication + Add-PodeWebPage -Name $cmd -Icon Settings -Layouts $tabs -Group $group -NoAuthentication:$NoAuthentication } } -function Use-PodeWebPages { +function Use-PodeWebPages +{ [CmdletBinding()] param( [Parameter()] @@ -1029,22 +933,19 @@ function Use-PodeWebPages { } } -function Get-PodeWebPage { - [CmdletBinding(DefaultParameterSetName = 'Name')] +function Get-PodeWebPage +{ + [CmdletBinding()] param( - [Parameter(ParameterSetName = 'Id')] - [string] - $Id, - - [Parameter(ParameterSetName = 'Name')] + [Parameter()] [string] $Name, - [Parameter(ParameterSetName = 'Name')] + [Parameter()] [string] $Group, - [Parameter(ParameterSetName = 'Name')] + [Parameter()] [switch] $NoGroup ) @@ -1055,177 +956,61 @@ function Get-PodeWebPage { return $null } - # if ID, check - if (![string]::IsNullOrWhiteSpace($Id)) { - return $pages[$Id] - } - - # get page values $pages = $pages.Values # filter by group if ($NoGroup -and [string]::IsNullOrWhiteSpace($Group)) { $pages = @(foreach ($page in $pages) { - if ([string]::IsNullOrWhiteSpace($page.Group)) { - $page - } - }) + if ([string]::IsNullOrWhiteSpace($page.Group)) { + $page + } + }) } elseif (![string]::IsNullOrWhiteSpace($Group)) { $pages = @(foreach ($page in $pages) { - if ($page.Group -ieq $Group) { - $page - } - }) + if ($page.Group -ieq $Group) { + $page + } + }) } # filter by page name if (![string]::IsNullOrWhiteSpace($Name)) { $pages = @(foreach ($page in $pages) { - if ($page.Name -ieq $Name) { - $page - } - }) + if ($page.Name -ieq $Name) { + $page + } + }) } # return filtered pages return $pages } -function Test-PodeWebPage { - [CmdletBinding(DefaultParameterSetName = 'Name')] - param( - [Parameter(Mandatory = $true, ParameterSetName = 'Id')] - [string] - $Id, - - [Parameter(Mandatory = $true, ParameterSetName = 'Name')] - [string] - $Name, - - [Parameter(ParameterSetName = 'Name')] - [string] - $Group, - - [Parameter(ParameterSetName = 'Name')] - [switch] - $NoGroup - ) - - # by ID - if (![string]::IsNullOrWhiteSpace($Id)) { - return (Get-PodeWebState -Name 'pages').ContainsKey($Id) - } - - # by Name/Group - else { - # get pages - $pages = Get-PodeWebPage -Name $Name -Group $Group -NoGroup:$NoGroup - - # are there any pages? - if ($null -eq $pages) { - return $false - } - - return (@($pages) | Measure-Object).Count -gt 0 - } -} - -function New-PodeWebPageGroup { +function Test-PodeWebPage +{ [CmdletBinding()] param( - [Parameter()] + [Parameter(Mandatory=$true)] [string] $Name, [Parameter()] [string] - $DisplayName, + $Group, [Parameter()] - [string] - $Icon, - [switch] - $NoCounter, - - [switch] - $Hide, - - [switch] - $PassThru - ) - - # test if page group exists - if (Test-PodeWebPageGroup -Name $Name) { - throw "Page Group already exists: $($Name)" - } - - # set display name - if ([string]::IsNullOrEmpty($DisplayName)) { - $DisplayName = $Name - } - - # setup group meta - $groupMeta = @{ - Operation = 'New' - ComponentType = 'Group' - ObjectType = 'Group' - ID = Get-PodeWebRandomName - Name = $Name - DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) - Icon = $Icon - NoCounter = $NoCounter.IsPresent - Hide = $Hide.IsPresent - Pages = @{} - } - - # add group meta to state - $groups = Get-PodeWebState -Name 'groups' - $groups[$Name] = $groupMeta - - if ($PassThru) { - return $groupMeta - } -} - -function Get-PodeWebPageGroup { - [CmdletBinding()] - param( - [Parameter()] - [string] - $Name = $null + $NoGroup ) - $groups = Get-PodeWebState -Name 'groups' + # get pages + $pages = Get-PodeWebPage -Name $Name -Group $Group -NoGroup:$NoGroup - # get all groups on null - if ($null -eq $Name) { - return $groups + # are there any pages? + if ($null -eq $pages) { + return $false } - # return specific group - return $groups[$Name] -} - -function Test-PodeWebPageGroup { - [CmdletBinding()] - param( - [Parameter()] - [string] - $Name - ) - - return (Get-PodeWebState -Name 'groups').ContainsKey($Name) -} - -function Remove-PodeWebPageGroup { - [CmdletBinding()] - param( - [Parameter()] - [string] - $Name - ) - - $null = (Get-PodeWebState -Name 'groups').Remove($Name) + return (@($pages) | Measure-Object).Count -gt 0 } From 210c3505d9e01db150e0f85633fd39a075c8ec13 Mon Sep 17 00:00:00 2001 From: KeithALane <142168155+KeithALane@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:29:24 -0400 Subject: [PATCH 2/2] Update Pages.ps1 Initially used the code from an older version of this file. This version should contain only the new changes. --- src/Public/Pages.ps1 | 1134 +++++++++++++++++++++++++----------------- 1 file changed, 677 insertions(+), 457 deletions(-) diff --git a/src/Public/Pages.ps1 b/src/Public/Pages.ps1 index cb4bbca1..0c86895b 100644 --- a/src/Public/Pages.ps1 +++ b/src/Public/Pages.ps1 @@ -1,8 +1,11 @@ -function Set-PodeWebLoginPage -{ +function Set-PodeWebLoginPage { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter()] + [string] + $Id, + + [Parameter(Mandatory = $true)] [string] $Authentication, @@ -11,12 +14,10 @@ function Set-PodeWebLoginPage $Content, [Parameter()] - [Alias('Icon')] [string] $Logo, [Parameter()] - [Alias('IconUrl')] [string] $LogoUrl, @@ -48,15 +49,26 @@ function Set-PodeWebLoginPage [string] $SignInMessage, + [Parameter()] + [string] + $LoginPath, + + [Parameter()] + [string] + $LogoutPath, + [switch] $PassThru ) # check content - if (!(Test-PodeWebContent -Content $Content -ComponentType Layout, Element)) { - throw 'The Login page can only contain layouts and/or elements' + if (!(Test-PodeWebContent -Content $Content -ComponentType Element)) { + throw 'The Login page can only contain other elements' } + # retrieve the auth from pode + $auth = Get-PodeAuth -Name $Authentication + # if no content, add default if (Test-PodeIsEmpty -Value $Content) { $Content = @( @@ -69,31 +81,28 @@ function Set-PodeWebLoginPage Set-PodeWebState -Name 'auth' -Value $Authentication Set-PodeWebState -Name 'auth-props' -Value @{ Username = $UsernameProperty - Group = $GroupProperty - Avatar = $AvatarProperty - Theme = $ThemeProperty - Logout = $true + Group = $GroupProperty + Avatar = $AvatarProperty + Theme = $ThemeProperty + Logout = $true } + # get home url + $sysUrls = Get-PodeWebState -Name 'system-urls' + # set a default logo/url if ([string]::IsNullOrWhiteSpace($Logo)) { - $Logo = '/pode.web/images/icon.png' + $Logo = '/pode.web-static/images/icon.png' } $Logo = (Add-PodeWebAppPath -Url $Logo) - if ([string]::IsNullOrWhiteSpace($LogoUrl)) { - $LogoUrl = '/' + if (![string]::IsNullOrWhiteSpace($LogoUrl)) { + $LogoUrl = (Add-PodeWebAppPath -Url $LogoUrl) } - $LogoUrl = (Add-PodeWebAppPath -Url $LogoUrl) # background image $BackgroundImage = (Add-PodeWebAppPath -Url $BackgroundImage) - # set default failure/success urls - $auth = Get-PodeAuth -Name $Authentication - $auth.Failure.Url = (Add-PodeWebAppPath -Url '/login') - $auth.Success.Url = (Add-PodeWebAppPath -Url '/') - # is this auto-redirect oauth2? $isOAuth2 = ($auth.Scheme.Scheme -ieq 'oauth2') @@ -102,91 +111,124 @@ function Set-PodeWebLoginPage $grantType = 'password' } - # route path - $routePath = '/login' + # generate page ID + $Id = Get-PodeWebPageId -Id $Id -Name 'login' -System + + # login / logout paths + if ([string]::IsNullOrEmpty($LoginPath)) { + $LoginPath = '/login' + } + + if ([string]::IsNullOrEmpty($LogoutPath)) { + $LogoutPath = '/logout' + } # setup page meta $pageMeta = @{ - ComponentType = 'Page' - ObjectType = 'Page' - Path = $routePath - Name = 'Login' - Content = $Content - SignInMessage = (Protect-PodeWebValue -Value $SignInMessage -Default 'Please sign in' -Encode) - IsSystem = $true + Operation = 'New' + ComponentType = 'Page' + ObjectType = 'Page' + ID = $Id + Route = @{ + Login = @{ + Path = (Get-PodeWebPagePath -Name 'login' -Path $LoginPath -NoAppPath) + Url = (Get-PodeWebPagePath -Name 'login' -Path $LoginPath) + } + Logout = @{ + Path = (Get-PodeWebPagePath -Name 'logout' -Path $LogoutPath -NoAppPath) + Url = (Get-PodeWebPagePath -Name 'logout' -Path $LogoutPath) + } + } + Name = 'Login' + Content = $Content + SignInMessage = (Protect-PodeWebValue -Value $SignInMessage -Default 'Please sign in' -Encode) + Logo = @{ + IconUrl = $Logo + Url = $LogoUrl + } + BackgroundImage = $BackgroundImage + CopyRight = $Copyright + Authentication = $Authentication + IsOAuth2 = $isOAuth2 + GrantType = $grantType + IsSystem = $true + ResponseType = (Get-PodeWebResponseType) + } + + # set auth system urls + $sysUrls.Login = $pageMeta.Route.Login + $sysUrls.Logout = $pageMeta.Route.Logout + + # set default failure/success urls + if ([string]::IsNullOrWhiteSpace($auth.Failure.Url)) { + $auth.Failure.Url = $pageMeta.Route.Login.Url + } + + if ([string]::IsNullOrWhiteSpace($auth.Success.Url)) { + $auth.Success.Url = $sysUrls.Home.Url } # add page meta to state $pages = Get-PodeWebState -Name 'pages' - $pages[$routePath] = $pageMeta + $pages[$Id] = $pageMeta # get the endpoints to bind $endpointNames = Get-PodeWebState -Name 'endpoint-name' # add the login route - Add-PodeRoute -Method Get -Path '/login' -Authentication $Authentication -ArgumentList @{ Path = $routePath } -EndpointName $endpointNames -Login -ScriptBlock { + Add-PodeRoute -Method Get -Path $pageMeta.Route.Login.Path -Authentication $Authentication -ArgumentList @{ ID = $Id } -EndpointName $endpointNames -Login -ScriptBlock { param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] Write-PodeWebViewResponse -Path 'login' -Data @{ - Page = $global:PageData - Content = $global:PageData.Content - Theme = Get-PodeWebTheme - Logo = $using:Logo - LogoUrl = $using:LogoUrl - Background = @{ - Image = $using:BackgroundImage + Page = $global:PageData + Theme = Get-PodeWebTheme + Sessions = @{ + Enabled = (Test-PodeSessionsEnabled) + Tabs = !(Test-PodeSessionScopeIsBrowser) } - SignInMessage = $global:PageData.SignInMessage - Copyright = $using:Copyright - Auth = @{ - Name = $using:Authentication - IsOAuth2 = $using:isOAuth2 - GrantType = $using:grantType + Logo = $PageData.Logo.IconUrl + LogoUrl = $PageData.Logo.Url + Background = @{ + Image = $PageData.BackgroundImage + } + SignInMessage = $PageData.SignInMessage + Copyright = $PageData.Copyright + Auth = @{ + Name = $PageData.Authentication + IsOAuth2 = $PageData.IsOAuth2 + GrantType = $PageData.GrantType } } $global:PageData = $null } - Add-PodeRoute -Method Post -Path '/login' -Authentication $Authentication -EndpointName $endpointNames -Login + Add-PodeRoute -Method Post -Path $pageMeta.Route.Login.Path -Authentication $Authentication -EndpointName $endpointNames -Login # add the logout route - Add-PodeRoute -Method Post -Path '/logout' -Authentication $Authentication -EndpointName $endpointNames -Logout + Add-PodeRoute -Method Post -Path $pageMeta.Route.Logout.Path -Authentication $Authentication -EndpointName $endpointNames -Logout - # add an authenticated home route - Remove-PodeWebRoute -Method Get -Path '/' -EndpointName $endpointNames + # login content + Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/content" -ArgumentList @{ ID = $Id } -EndpointName $endpointNames -ScriptBlock { + param($Data) + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + Write-PodeJsonResponse -Value $global:PageData.Content + $global:PageData = $null + } - Add-PodeRoute -Method Get -Path '/' -Authentication $Authentication -EndpointName $endpointNames -ScriptBlock { - $page = Get-PodeWebFirstPublicPage - if ($null -ne $page) { - Move-PodeResponseUrl -Url (Get-PodeWebPagePath -Page $page) + # add sse open route + if (Test-PodeWebResponseType -Type Sse) { + Add-PodeRoute -Method Get -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-open" -ArgumentList @{ ID = $Id } -EndpointName $EndpointName -ScriptBlock { + param($Data) + ConvertTo-PodeSseConnection -Name 'Pode.Web.Actions' -Group $Data.ID } - $authData = Get-PodeWebAuthData - $username = Get-PodeWebAuthUsername -AuthData $authData - $groups = Get-PodeWebAuthGroups -AuthData $authData - $avatar = Get-PodeWebAuthAvatarUrl -AuthData $authData - $theme = Get-PodeWebTheme - $navigation = Get-PodeWebNavDefault - - Write-PodeWebViewResponse -Path 'index' -Data @{ - Page = @{ - Name = 'Home' - Title = 'Home' - DisplayName = 'Home' - Path = '/' - IsSystem = $true - } - Theme = $theme - Navigation = $navigation - Auth = @{ - Enabled = $true - Logout = (Get-PodeWebState -Name 'auth-props').Logout - Authenticated = $authData.IsAuthenticated - Username = $username - Groups = $groups - Avatar = $avatar + # add sse close route + Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-close" -EndpointName $EndpointName -ScriptBlock { + $clientId = Get-PodeHeader -Name 'X-PODE-SSE-CLIENT-ID' + if (![string]::IsNullOrEmpty($clientId)) { + Close-PodeSseConnection -Name 'Pode.Web.Actions' -ClientId $clientId } } } @@ -196,136 +238,32 @@ function Set-PodeWebLoginPage } } -function Set-PodeWebHomePage -{ +function Add-PodeWebPage { [CmdletBinding()] param( [Parameter()] - [hashtable[]] - $Layouts, + [string] + $Id, - [Parameter()] + [Parameter(Mandatory = $true)] [string] - $DisplayName, + $Name, [Parameter()] [string] - $Title, + $Path, [Parameter()] - [hashtable[]] - $Navigation, + [object[]] + $Middleware, [Parameter()] - [Alias('NoAuth')] - [switch] - $NoAuthentication, - - [switch] - $NoTitle, - - [switch] - $PassThru - ) - - # ensure layouts are correct - if (!(Test-PodeWebContent -Content $Layouts -ComponentType Layout)) { - throw 'The Home Page can only contain layouts' - } - - # set page title - if ([string]::IsNullOrWhiteSpace($DisplayName)) { - $DisplayName = 'Home' - } - - if ([string]::IsNullOrWhiteSpace($Title)) { - $Title = $DisplayName - } - - # route path - $routePath = '/' - - # setup page meta - $pageMeta = @{ - ComponentType = 'Page' - ObjectType = 'Page' - Path = $routePath - Name = 'Home' - Title = [System.Net.WebUtility]::HtmlEncode($Title) - DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) - NoTitle = $NoTitle.IsPresent - Navigation = $Navigation - Layouts = $Layouts - IsSystem = $true - } - - # add page meta to state - $pages = Get-PodeWebState -Name 'pages' - $pages[$routePath] = $pageMeta - - # does the page need auth? - $auth = $null - if (!$NoAuthentication) { - $auth = (Get-PodeWebState -Name 'auth') - } - - # get the endpoints to bind - $endpointNames = Get-PodeWebState -Name 'endpoint-name' - - # remove route - Remove-PodeWebRoute -Method Get -Path $routePath -EndpointName $endpointNames - - # re-add route - Add-PodeRoute -Method Get -Path $routePath -Authentication $auth -ArgumentList @{ Path = $routePath } -EndpointName $endpointNames -ScriptBlock { - param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] - - # we either render the home page, or move to the first page if home page is blank - $comps = $global:PageData.Layouts - if (($null -eq $comps) -or ($comps.Length -eq 0)) { - $page = Get-PodeWebFirstPublicPage - if ($null -ne $page) { - Move-PodeResponseUrl -Url (Get-PodeWebPagePath -Page $page) - } - } - - $authData = Get-PodeWebAuthData - $username = Get-PodeWebAuthUsername -AuthData $authData - $groups = Get-PodeWebAuthGroups -AuthData $authData - $avatar = Get-PodeWebAuthAvatarUrl -AuthData $authData - $theme = Get-PodeWebTheme - $navigation = Get-PodeWebNavDefault -Items $global:PageData.Navigation - - Write-PodeWebViewResponse -Path 'index' -Data @{ - Page = $global:PageData - Theme = $theme - Navigation = $navigation - Layouts = $comps - Auth = @{ - Enabled = ![string]::IsNullOrWhiteSpace((Get-PodeWebState -Name 'auth')) - Logout = (Get-PodeWebState -Name 'auth-props').Logout - Authenticated = $authData.IsAuthenticated - Username = $username - Groups = $groups - Avatar = $avatar - } - } + [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + $IfExists = 'Default', - $global:PageData = $null - } - - if ($PassThru) { - return $pageMeta - } -} - -function Add-PodeWebPage -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $Name, + [Parameter()] + [int] + $Index = [int]::MaxValue, [Parameter()] [string] @@ -336,8 +274,9 @@ function Add-PodeWebPage $Title, [Parameter()] + [ValidateNotNull()] [string] - $Group, + $Group = [string]::Empty, [Parameter()] [ValidateNotNullOrEmpty()] @@ -346,7 +285,7 @@ function Add-PodeWebPage [Parameter()] [hashtable[]] - $Layouts, + $Content, [Parameter()] [scriptblock] @@ -399,17 +338,31 @@ function Add-PodeWebPage [switch] $NoSidebar, + [switch] + $NoNavigation, + + [switch] + $HomePage, + [switch] $PassThru ) - # ensure layouts are correct - if (!(Test-PodeWebContent -Content $Layouts -ComponentType Layout)) { - throw 'A Page can only contain layouts' + # ensure elements are correct + if (!(Test-PodeWebContent -Content $Content -ComponentType Element)) { + throw 'A Page can only contain elements' + } + + # test if group exists - otherwise create a basic group entry + if (!(Test-PodeWebPageGroup -Name $Group)) { + New-PodeWebPageGroup -Name $Group } + # generate page ID + $Id = Get-PodeWebPageId -Id $Id -Name $Name -Group $Group + # test if page/page-link exists - if (Test-PodeWebPage -Name $Name -Group $Group -NoGroup) { + if (Test-PodeWebPage -Id $Id) { throw "Web page/link already exists: $($Name) [Group: $($Group)]" } @@ -422,150 +375,259 @@ function Add-PodeWebPage $Title = $DisplayName } - # build the route path - $routePath = (Get-PodeWebPagePath -Name $Name -Group $Group -NoAppPath) + # check for scoped vars + $ScriptBlock, $mainUsingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + $HelpScriptBlock, $helpUsingVars = Convert-PodeScopedVariables -ScriptBlock $HelpScriptBlock -PSSession $PSCmdlet.SessionState # setup page meta $pageMeta = @{ - ComponentType = 'Page' - ObjectType = 'Page' - Path = $routePath - Name = $Name - Title = [System.Net.WebUtility]::HtmlEncode($Title) - DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) - NoTitle = $NoTitle.IsPresent - NoBackArrow = $NoBackArrow.IsPresent - NoBreadcrumb = $NoBreadcrumb.IsPresent - NewTab = $NewTab.IsPresent - IsDynamic = $false - ShowHelp = ($null -ne $HelpScriptBlock) - Icon = $Icon - Group = $Group - Url = (Get-PodeWebPagePath -Name $Name -Group $Group) - Hide = $Hide.IsPresent - NoSidebar = $NoSidebar.IsPresent - Navigation = $Navigation - ScriptBlock = $ScriptBlock - HelpScriptBlock = $HelpScriptBlock - Layouts = $Layouts + Operation = 'New' + ComponentType = 'Page' + ObjectType = 'Page' + ID = $Id + Index = $Index + Group = $Group + Name = $Name + Title = [System.Net.WebUtility]::HtmlEncode($Title) + DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) + NoTitle = $NoTitle.IsPresent + NoBackArrow = $NoBackArrow.IsPresent + NoBreadcrumb = $NoBreadcrumb.IsPresent + NewTab = $NewTab.IsPresent + IsDynamic = $false + ShowHelp = ($null -ne $HelpScriptBlock) + Icon = $Icon + Path = (Get-PodeWebPagePath -Name $Name -Group $Group -Path $Path -NoAppPath) + Url = (Get-PodeWebPagePath -Name $Name -Group $Group -Path $Path) + Hide = $Hide.IsPresent + NoSidebar = $NoSidebar.IsPresent + NoNavigation = $NoNavigation.IsPresent + Navigation = $Navigation + Logic = @{ + ScriptBlock = $ScriptBlock + UsingVariables = $mainUsingVars + } + Help = @{ + ScriptBlock = $HelpScriptBlock + UsingVariables = $helpUsingVars + } + Content = $Content + Authentication = $null NoAuthentication = $NoAuthentication.IsPresent - Access = @{ + IsHomePage = $HomePage.IsPresent + Access = @{ Groups = @($AccessGroups) - Users = @($AccessUsers) + Users = @($AccessUsers) } + ResponseType = (Get-PodeWebResponseType) } - # add page meta to state - $pages = Get-PodeWebState -Name 'pages' - $pages[$routePath] = $pageMeta - # does the page need auth? $auth = $null - if (!$NoAuthentication) { - $auth = (Get-PodeWebState -Name 'auth') + if (!$pageMeta.NoAuthentication) { + $auth = Get-PodeWebState -Name 'auth' } + $pageMeta.Authentication = $auth + + # add page meta to state + Register-PodeWebPage -Metadata $pageMeta # get the endpoints to bind if (Test-PodeIsEmpty $EndpointName) { $EndpointName = Get-PodeWebState -Name 'endpoint-name' } + # remove the "root" page, if "root-redirect" was originally flagged and this page is for the root path + if (($pageMeta.Path -eq '/') -and (Get-PodeWebState -Name 'root-redirect')) { + Remove-PodeRoute -Method Get -Path '/' + } + # add the page route - Add-PodeRoute -Method Get -Path $routePath -Authentication $auth -ArgumentList @{ Data = $ArgumentList; Path = $routePath } -EndpointName $EndpointName -ScriptBlock { + Add-PodeRoute -Method Get -Path $pageMeta.Path -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -Middleware $Middleware -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] - if (!$global:PageData.NoBackArrow) { - $global:PageData.ShowBack = (($null -ne $WebEvent.Query) -and ($WebEvent.Query.Count -gt 0)) - if ($global:PageData.ShowBack -and ($WebEvent.Query.Count -eq 1) -and ($WebEvent.Query.ContainsKey(''))) { - $global:PageData.ShowBack = $false + # get auth details of a user + $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) + $authMeta = $null + + if ($authEnabled) { + $authData = Get-PodeAuthUser + $authMeta = @{ + Enabled = $true + Logout = (Get-PodeWebState -Name 'auth-props').Logout + Authenticated = ($null -ne $authData) } + + if ($authMeta.Authenticated) { + $authMeta['Username'] = Get-PodeWebAuthUsername -User $authData + $authMeta['Groups'] = Get-PodeWebAuthGroups -User $authData + $authMeta['Avatar'] = Get-PodeWebAuthAvatarUrl -User $authData + } + } + + # check access - 403 if denied + if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { + Set-PodeResponseStatus -Code 403 } + else { - $global:PageData.ShowBack = $false + # show a back arrow? + if (!$global:PageData.NoBackArrow) { + $global:PageData.ShowBack = (($null -ne $WebEvent.Query) -and ($WebEvent.Query.Count -gt 0)) + if ($global:PageData.ShowBack -and ($WebEvent.Query.Count -eq 1) -and ($WebEvent.Query.ContainsKey(''))) { + $global:PageData.ShowBack = $false + } + } + else { + $global:PageData.ShowBack = $false + } + + # render the page + Write-PodeWebViewResponse -Path 'index' -Data @{ + Page = $global:PageData + Title = $global:PageData.Title + DisplayName = $global:PageData.DisplayName + Theme = (Get-PodeWebTheme) + Sessions = @{ + Enabled = (Test-PodeSessionsEnabled) + Tabs = !(Test-PodeSessionScopeIsBrowser) + } + Auth = $authMeta + } } + $global:PageData = $null + } + + Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/content" -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { + param($Data) + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + Set-PodeWebMetadata + # get auth details of a user - $authData = Get-PodeWebAuthData - $username = Get-PodeWebAuthUsername -AuthData $authData - $groups = Get-PodeWebAuthGroups -AuthData $authData - $avatar = Get-PodeWebAuthAvatarUrl -AuthData $authData - $theme = Get-PodeWebTheme - $navigation = Get-PodeWebNavDefault -Items $global:PageData.Navigation - - $authMeta = @{ - Enabled = ![string]::IsNullOrWhiteSpace((Get-PodeWebState -Name 'auth')) - Logout = (Get-PodeWebState -Name 'auth-props').Logout - Authenticated = $authData.IsAuthenticated - Username = $username - Groups = $groups - Avatar = $avatar + $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) + $authMeta = $null + + if ($authEnabled) { + $authData = Get-PodeAuthUser + if ($null -ne $authData) { + $authMeta = @{ + Username = (Get-PodeWebAuthUsername -User $authData) + Groups = (Get-PodeWebAuthGroups -User $authData) + } + } } # check access - 403 if denied if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { Set-PodeResponseStatus -Code 403 } - else { - # if we have a scriptblock, invoke that to get dynamic components - $layouts = $null - if ($null -ne $global:PageData.ScriptBlock) { - $layouts = Invoke-PodeScriptBlock -ScriptBlock $global:PageData.ScriptBlock -Arguments $Data.Data -Splat -Return + # if we have a scriptblock, invoke that to get dynamic elements + $content = $null + if ($null -ne $global:PageData.Logic.ScriptBlock) { + $content = Invoke-PodeWebScriptBlock -Logic $global:PageData.Logic -Arguments $Data.Data } - if (($null -eq $layouts) -or ($layouts.Length -eq 0)) { - $layouts = $global:PageData.Layouts + if (($null -eq $content) -or ($content.Length -eq 0)) { + $content = $global:PageData.Content } - $breadcrumb = $null - $filteredLayouts = @() + $navigation = Get-PodeWebNavDefault -Items $global:PageData.Navigation + Write-PodeJsonResponse -Value (@($navigation) + @($content)) + } - foreach ($item in $layouts) { - if ($item.ObjectType -ieq 'breadcrumb') { - if ($null -ne $breadcrumb) { - throw "Cannot set two brecrumb trails on one page" - } + $global:PageData = $null + } - $breadcrumb = $item - } - else { - $filteredLayouts += $item + # add sse open route + if (Test-PodeWebResponseType -Type Sse) { + Add-PodeRoute -Method Get -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-open" -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { + param($Data) + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + + # get auth details of a user + $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) + $authMeta = $null + + if ($authEnabled) { + $authData = Get-PodeAuthUser + if ($null -ne $authData) { + $authMeta = @{ + Username = (Get-PodeWebAuthUsername -User $authData) + Groups = (Get-PodeWebAuthGroups -User $authData) + } } } - Write-PodeWebViewResponse -Path 'index' -Data @{ - Page = $global:PageData - Title = $global:PageData.Title - DisplayName = $global:PageData.DisplayName - Theme = $theme - Navigation = $navigation - Breadcrumb = $breadcrumb - Layouts = $filteredLayouts - Auth = $authMeta + # check access - 403 if denied + if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { + Set-PodeResponseStatus -Code 403 } + else { + # open new sse connection + ConvertTo-PodeSseConnection -Name 'Pode.Web.Actions' -Group $Data.ID + } + + $global:PageData = $null } - $global:PageData = $null + # add sse close route + Add-PodeRoute -Method Post -Path "/pode.web-dynamic/pages/$($pageMeta.ID)/sse-close" -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { + param($Data) + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + + # get auth details of a user + $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) + $authMeta = $null + + if ($authEnabled) { + $authData = Get-PodeAuthUser + if ($null -ne $authData) { + $authMeta = @{ + Username = (Get-PodeWebAuthUsername -User $authData) + Groups = (Get-PodeWebAuthGroups -User $authData) + } + } + } + + # check access - 403 if denied + if (!(Test-PodeWebPageAccess -PageAccess $global:PageData.Access -Auth $authMeta)) { + Set-PodeResponseStatus -Code 403 + } + else { + # if a connection in header, close connection + $clientId = Get-PodeHeader -Name 'X-PODE-SSE-CLIENT-ID' + if (![string]::IsNullOrEmpty($clientId)) { + Close-PodeSseConnection -Name 'Pode.Web.Actions' -ClientId $clientId + } + } + + $global:PageData = $null + } } # add the page help route - $helpPath = "$($routePath)/help" + $helpPath = "/pode.web-dynamic/pages/$($pageMeta.ID)/help" if (($null -ne $HelpScriptBlock) -and !(Test-PodeWebRoute -Path $helpPath)) { - Add-PodeRoute -Method Post -Path $helpPath -Authentication $auth -ArgumentList @{ Data = $ArgumentList; Path = $routePath } -EndpointName $EndpointName -ScriptBlock { + Add-PodeRoute -Method Post -Path $helpPath -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { param($Data) - $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.Path] + $global:PageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + Set-PodeWebMetadata # get auth details of a user - $authData = Get-PodeWebAuthData - $username = Get-PodeWebAuthUsername -AuthData $authData - $groups = Get-PodeWebAuthGroups -AuthData $authData - - $authMeta = @{ - Enabled = ![string]::IsNullOrWhiteSpace((Get-PodeWebState -Name 'auth')) - Authenticated = $authData.IsAuthenticated - Username = $username - Groups = $groups + $authEnabled = ![string]::IsNullOrEmpty((Get-PodeWebState -Name 'auth')) + $authMeta = $null + + if ($authEnabled) { + $authData = Get-PodeAuthUser + if ($null -ne $authData) { + $authMeta = @{ + Username = (Get-PodeWebAuthUsername -User $authData) + Groups = (Get-PodeWebAuthGroups -User $authData) + } + } } # check access - 403 if denied @@ -573,12 +635,9 @@ function Add-PodeWebPage Set-PodeResponseStatus -Code 403 } else { - $result = Invoke-PodeScriptBlock -ScriptBlock $global:PageData.HelpScriptBlock -Arguments $Data.Data -Splat -Return - if ($null -eq $result) { - $result = @() - } + $result = Invoke-PodeWebScriptBlock -Logic $global:PageData.Help -Arguments $Data.Data - if (!$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { + if (($null -ne $result) -and !$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { Write-PodeJsonResponse -Value $result } } @@ -592,36 +651,52 @@ function Add-PodeWebPage } } -function Add-PodeWebPageLink -{ - [CmdletBinding(DefaultParameterSetName='ScriptBlock')] +function Add-PodeWebPageLink { + [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')] param( - [Parameter(Mandatory=$true)] + [Parameter()] + [string] + $Id, + + [Parameter(Mandatory = $true)] [string] $Name, + [Parameter(ParameterSetName = 'ScriptBlock')] + [object[]] + $Middleware, + + [Parameter()] + [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + $IfExists = 'Default', + + [Parameter()] + [int] + $Index = [int]::MaxValue, + [Parameter()] [string] $DisplayName, [Parameter()] + [ValidateNotNull()] [string] - $Group, + $Group = [string]::Empty, [Parameter()] [ValidateNotNullOrEmpty()] [string] $Icon = 'file', - [Parameter(Mandatory=$true, ParameterSetName='ScriptBlock')] + [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')] [scriptblock] $ScriptBlock, - [Parameter(ParameterSetName='ScriptBlock')] + [Parameter(ParameterSetName = 'ScriptBlock')] [object[]] $ArgumentList, - [Parameter(Mandatory=$true, ParameterSetName='Url')] + [Parameter(Mandatory = $true, ParameterSetName = 'Url')] [string] $Url, @@ -633,16 +708,16 @@ function Add-PodeWebPageLink [string[]] $AccessUsers = @(), - [Parameter(ParameterSetName='ScriptBlock')] + [Parameter(ParameterSetName = 'ScriptBlock')] [string[]] $EndpointName, - [Parameter(ParameterSetName='ScriptBlock')] + [Parameter(ParameterSetName = 'ScriptBlock')] [Alias('NoAuth')] [switch] $NoAuthentication, - [Parameter(ParameterSetName='Url')] + [Parameter(ParameterSetName = 'Url')] [switch] $NewTab, @@ -650,8 +725,16 @@ function Add-PodeWebPageLink $Hide ) + # test if group exists - otherwise create a basic group entry + if (!(Test-PodeWebPageGroup -Name $Group)) { + New-PodeWebPageGroup -Name $Group + } + + # generate page ID + $Id = Get-PodeWebPageId -Id $Id -Name $Name -Group $Group + # test if page/page-link exists - if (Test-PodeWebPage -Name $Name -Group $Group -NoGroup) { + if (Test-PodeWebPage -Id $Id) { throw "Web page/link already exists: $($Name) [Group: $($Group)]" } @@ -660,65 +743,78 @@ function Add-PodeWebPageLink $DisplayName = $Name } + # check for scoped vars + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + # setup page meta $pageMeta = @{ - ComponentType = 'Page' - ObjectType = 'Link' - Name = $Name - DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) - NewTab = $NewTab.IsPresent - Icon = $Icon - Group = $Group - Url = (Add-PodeWebAppPath -Url $Url) - Hide = $Hide.IsPresent - IsDynamic = ($null -ne $ScriptBlock) - ScriptBlock = $ScriptBlock - Access = @{ + Operation = 'New' + ComponentType = 'Page' + ObjectType = 'Link' + ID = $Id + Index = $Index + Name = $Name + Group = $Group + DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) + NewTab = $NewTab.IsPresent + Icon = $Icon + Path = (Get-PodeWebPagePath -Name $Name -Group $Group -NoAppPath) + Url = (Add-PodeWebAppPath -Url $Url) + Hide = $Hide.IsPresent + IsDynamic = ($null -ne $ScriptBlock) + Logic = @{ + ScriptBlock = $ScriptBlock + UsingVariables = $usingVars + } + Authentication = $null + NoAuthentication = $NoAuthentication.IsPresent + Access = @{ Groups = @($AccessGroups) - Users = @($AccessUsers) + Users = @($AccessUsers) } - NoEvents = $true + NoEvents = $true } - # build the route path - $routePath = (Get-PodeWebPagePath -Name $Name -Group $Group -NoAppPath) + # does the page need auth? + $auth = [string]::Empty + if (!$pageMeta.NoAuthentication) { + $auth = Get-PodeWebState -Name 'auth' + } + $pageMeta.Authentication = $auth # add page meta to state - $pages = Get-PodeWebState -Name 'pages' - $pages[$routePath] = $pageMeta + Register-PodeWebPage -Metadata $pageMeta # add page link - if (($null -ne $ScriptBlock) -and !(Test-PodeWebRoute -Path $routePath)) { - $auth = $null - if (!$NoAuthentication) { - $auth = (Get-PodeWebState -Name 'auth') - } - + if (($null -ne $ScriptBlock) -and !(Test-PodeWebRoute -Path $pageMeta.Path)) { if (Test-PodeIsEmpty $EndpointName) { $EndpointName = Get-PodeWebState -Name 'endpoint-name' } - Add-PodeRoute -Method Post -Path $routePath -Authentication $auth -ArgumentList @{ Data = $ArgumentList; Path = $routePath } -EndpointName $EndpointName -ScriptBlock { + # remove the "root" page, if "root-redirect" was originally flagged and this page is for the root path + if (($pageMeta.Path -eq '/') -and (Get-PodeWebState -Name 'root-redirect')) { + Remove-PodeRoute -Method Get -Path '/' + } + + # add the route + Add-PodeRoute -Method Post -Path $pageMeta.Path -Authentication $pageMeta.Authentication -ArgumentList @{ Data = $ArgumentList; ID = $Id } -Middleware $Middleware -IfExists $IfExists -EndpointName $EndpointName -ScriptBlock { param($Data) - $pageData = (Get-PodeWebState -Name 'pages')[$Data.Path] + $pageData = (Get-PodeWebState -Name 'pages')[$Data.ID] + Set-PodeWebMetadata - $result = Invoke-PodeScriptBlock -ScriptBlock $pageData.ScriptBlock -Arguments $Data.Data -Splat -Return - if ($null -eq $result) { - $result = @() - } + $result = Invoke-PodeWebScriptBlock -Logic $pageData.Logic -Arguments $Data.Data - if (!$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { + if (($null -ne $result) -and !$WebEvent.Response.Headers.ContainsKey('Content-Disposition')) { Write-PodeJsonResponse -Value $result } } } } -function ConvertTo-PodeWebPage -{ +function ConvertTo-PodeWebPage { [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [string[]] $Commands, @@ -744,7 +840,7 @@ function ConvertTo-PodeWebPage Import-PodeModule -Name $Module Export-PodeModule -Name $Module - Write-Verbose "Getting exported commands from module" + Write-Verbose 'Getting exported commands from module' $ModuleCommands = (Get-Module -Name $Module | Sort-Object -Descending | Select-Object -First 1).ExportedCommands.Keys # if commands were supplied validate them - otherwise use all exported ones @@ -767,20 +863,8 @@ function ConvertTo-PodeWebPage throw 'No commands supplied to convert to Pages' } - $sysParams = @( - 'Verbose', - 'Debug', - 'ErrorAction', - 'WarningAction', - 'InformationAction', - 'ErrorVariable', - 'WarningVariable', - 'InformationVariable', - 'OutVariable', - 'OutBuffer', - 'PipelineVariable' - ) - + $sysParams = [System.Management.Automation.PSCmdlet]::CommonParameters.GetEnumerator() | Foreach-Object { $_ } + # create the pages for each of the commands foreach ($cmd in $Commands) { Write-Verbose "Building page for $($cmd)" @@ -801,99 +885,111 @@ function ConvertTo-PodeWebPage } $tabs = New-PodeWebTabs -Tabs @(foreach ($set in $sets) { - $elements = @(foreach ($param in $set.Parameters) { - if ($sysParams -icontains $param.Name) { - continue + # build name + $name = $set.Name + if ([string]::IsNullOrWhiteSpace($name) -or ($set.Name -iin @('__AllParameterSets'))) { + $name = 'Default' } - $type = $param.ParameterType.Name + # build input controls + $elements = @(foreach ($param in $set.Parameters) { + if ($sysParams -icontains $param.Name) { + continue + } - $default = $null - if ($null -ne $paramDefs) { - $default = ($paramDefs | Where-Object { $_.DefaultValue -and $_.Name.Extent.Text -ieq "`$$($param.Name)" }).DefaultValue.Value - } + $type = $param.ParameterType.Name - if ($type -iin @('boolean', 'switchparameter')) { - New-PodeWebCheckbox -Name $param.Name -AsSwitch - } - else { - switch ($type) { - 'pscredential' { - New-PodeWebCredential -Name $param.Name + $default = $null + if ($null -ne $paramDefs) { + $default = ($paramDefs | Where-Object { $_.DefaultValue -and $_.Name.Extent.Text -ieq "`$$($param.Name)" }).DefaultValue.Value } - default { - $multiple = $param.ParameterType.Name.EndsWith('[]') - - if ($param.Attributes.TypeId.Name -icontains 'ValidateSetAttribute') { - $values = ($param.Attributes | Where-Object { $_.TypeId.Name -ieq 'ValidateSetAttribute' }).ValidValues - New-PodeWebSelect -Name $param.Name -Options $values -SelectedValue $default -Multiple:$multiple - } - elseif ($param.ParameterType.BaseType.Name -ieq 'enum') { - $values = [enum]::GetValues($param.ParameterType) - New-PodeWebSelect -Name $param.Name -Options $values -SelectedValue $default -Multiple:$multiple - } - else { - New-PodeWebTextbox -Name $param.Name -Value $default + if ($type -iin @('boolean', 'switchparameter')) { + New-PodeWebCheckbox -Name "$($name)_$($param.Name)" -DisplayName $param.Name -AsSwitch + } + else { + switch ($type) { + 'pscredential' { + New-PodeWebCredential -Name "$($name)_$($param.Name)" -DisplayName $param.Name + } + + default { + $multiple = $param.ParameterType.Name.EndsWith('[]') + + if ($param.Attributes.TypeId.Name -icontains 'ValidateSetAttribute') { + $values = ($param.Attributes | Where-Object { $_.TypeId.Name -ieq 'ValidateSetAttribute' }).ValidValues + New-PodeWebSelect -Name "$($name)_$($param.Name)" -DisplayName $param.Name -Options $values -SelectedValue $default -Multiple:$multiple + } + elseif ($param.ParameterType.BaseType.Name -ieq 'enum') { + $values = [enum]::GetValues($param.ParameterType) + New-PodeWebSelect -Name "$($name)_$($param.Name)" -DisplayName $param.Name -Options $values -SelectedValue $default -Multiple:$multiple + } + else { + New-PodeWebTextbox -Name "$($name)_$($param.Name)" -DisplayName $param.Name -Value $default + } + } } } - } - } - }) + }) - $elements += (New-PodeWebHidden -Name '_Function_Name_' -Value $cmd) + $elements += (New-PodeWebHidden -Name '_Function_Name_' -Value $cmd) + $elements += (New-PodeWebHidden -Name '_Parameter_Set_Name_' -Value $name) - $name = $set.Name - if ([string]::IsNullOrWhiteSpace($name) -or ($set.Name -iin @('__AllParameterSets'))) { - $name = 'Default' - } + # build form + $formId = "form_param_$($cmd)_$($name)" + $form = New-PodeWebForm -Name "$($name)_Parameters_Form" -Id $formId -Content $elements -NoAuthentication:$NoAuthentication -ScriptBlock { + $cmd = $WebEvent.Data['_Function_Name_'] + $WebEvent.Data.Remove('_Function_Name_') - $formId = "form_param_$($cmd)_$($name)" + $setName = $WebEvent.Data['_Parameter_Set_Name_'] + $WebEvent.Data.Remove('_Parameter_Set_Name_') - $form = New-PodeWebForm -Name Parameters -Id $formId -Content $elements -AsCard -NoAuthentication:$NoAuthentication -ScriptBlock { - $cmd = $WebEvent.Data['_Function_Name_'] - $WebEvent.Data.Remove('_Function_Name_') + $_args = @{} + foreach ($key in $WebEvent.Data.Keys) { + $argKey = $key -ireplace "$($setName)_", '' - $_args = @{} - foreach ($key in $WebEvent.Data.Keys) { - if ($key -imatch '(?.+)_(Username|Password)$') { - $name = $Matches['name'] - $uKey = "$($name)_Username" - $pKey = "$($name)_Password" + if ($argKey -imatch '(?.+)_(Username|Password)$') { + $name = $Matches['name'] + $uKey = "$($argKey)_$($name)_Username" + $pKey = "$($argKey)_$($name)_Password" - if (![string]::IsNullOrWhiteSpace($WebEvent.Data[$uKey]) -and ![string]::IsNullOrWhiteSpace($WebEvent.Data[$pKey])) { - $creds = (New-Object System.Management.Automation.PSCredential -ArgumentList $WebEvent.Data[$uKey], (ConvertTo-SecureString -AsPlainText $WebEvent.Data[$pKey] -Force)) - $_args[$name] = $creds - } - } - else { - if ($WebEvent.Data[$key] -iin @('true', 'false')) { - $_args[$key] = ($WebEvent.Data[$key] -ieq 'true') + if (![string]::IsNullOrWhiteSpace($WebEvent.Data[$uKey]) -and ![string]::IsNullOrWhiteSpace($WebEvent.Data[$pKey])) { + $creds = (New-Object System.Management.Automation.PSCredential -ArgumentList $WebEvent.Data[$uKey], (ConvertTo-SecureString -AsPlainText $WebEvent.Data[$pKey] -Force)) + $_args[$name] = $creds + } } else { - if ($WebEvent.Data[$key].Contains(',')) { - $_args[$key] = ($WebEvent.Data[$key] -isplit ',' | ForEach-Object { $_.Trim() }) + if ($WebEvent.Data[$key] -iin @('true', 'false')) { + $_args[$argKey] = ($WebEvent.Data[$key] -ieq 'true') } else { - $_args[$key] = $WebEvent.Data[$key] + if ($WebEvent.Data[$key].Contains(',')) { + $_args[$argKey] = ($WebEvent.Data[$key] -isplit ',' | ForEach-Object { $_.Trim() }) + } + else { + $_args[$argKey] = $WebEvent.Data[$key] + } } } } - } - try { - (. $cmd @_args) | Out-PodeWebTextbox -Multiline -Preformat - } - catch { - $_.Exception | Out-PodeWebTextbox -Multiline -Preformat + try { + (. $cmd @_args) | + New-PodeWebTextbox -Name 'Output_Result' -Multiline -Preformat | + Out-PodeWebElement + } + catch { + $_.Exception | + New-PodeWebTextbox -Name 'Output_Error' -Multiline -Preformat | + Out-PodeWebElement + } } - } - New-PodeWebTab -Name $name -Layouts $form - }) + $card = New-PodeWebCard -Name "$($name)_Parameters" -DisplayName 'Parameters' -Content $form + New-PodeWebTab -Name $name -Content $card + }) if ($group -eq [string]::Empty) { #Added by Keith 09/22/2025 to allow group names to be used with ConvertTo-PodeWebPage - if ($GroupVerbs) { $group = $cmdInfo.Verb if ([string]::IsNullOrWhiteSpace($group)) { @@ -901,12 +997,17 @@ function ConvertTo-PodeWebPage } } } - Add-PodeWebPage -Name $cmd -Icon Settings -Layouts $tabs -Group $group -NoAuthentication:$NoAuthentication + + Add-PodeWebPage ` + -Name $cmd ` + -Icon Settings ` + -Content $tabs ` + -Group $group ` + -NoAuthentication:$NoAuthentication } } -function Use-PodeWebPages -{ +function Use-PodeWebPages { [CmdletBinding()] param( [Parameter()] @@ -933,19 +1034,22 @@ function Use-PodeWebPages } } -function Get-PodeWebPage -{ - [CmdletBinding()] +function Get-PodeWebPage { + [CmdletBinding(DefaultParameterSetName = 'Name')] param( - [Parameter()] + [Parameter(ParameterSetName = 'Id')] + [string] + $Id, + + [Parameter(ParameterSetName = 'Name')] [string] $Name, - [Parameter()] + [Parameter(ParameterSetName = 'Name')] [string] $Group, - [Parameter()] + [Parameter(ParameterSetName = 'Name')] [switch] $NoGroup ) @@ -956,61 +1060,177 @@ function Get-PodeWebPage return $null } + # if ID, check + if (![string]::IsNullOrWhiteSpace($Id)) { + return $pages[$Id] + } + + # get page values $pages = $pages.Values # filter by group if ($NoGroup -and [string]::IsNullOrWhiteSpace($Group)) { $pages = @(foreach ($page in $pages) { - if ([string]::IsNullOrWhiteSpace($page.Group)) { - $page - } - }) + if ([string]::IsNullOrWhiteSpace($page.Group)) { + $page + } + }) } elseif (![string]::IsNullOrWhiteSpace($Group)) { $pages = @(foreach ($page in $pages) { - if ($page.Group -ieq $Group) { - $page - } - }) + if ($page.Group -ieq $Group) { + $page + } + }) } # filter by page name if (![string]::IsNullOrWhiteSpace($Name)) { $pages = @(foreach ($page in $pages) { - if ($page.Name -ieq $Name) { - $page - } - }) + if ($page.Name -ieq $Name) { + $page + } + }) } # return filtered pages return $pages } -function Test-PodeWebPage -{ +function Test-PodeWebPage { + [CmdletBinding(DefaultParameterSetName = 'Name')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Id')] + [string] + $Id, + + [Parameter(Mandatory = $true, ParameterSetName = 'Name')] + [string] + $Name, + + [Parameter(ParameterSetName = 'Name')] + [string] + $Group, + + [Parameter(ParameterSetName = 'Name')] + [switch] + $NoGroup + ) + + # by ID + if (![string]::IsNullOrWhiteSpace($Id)) { + return (Get-PodeWebState -Name 'pages').ContainsKey($Id) + } + + # by Name/Group + else { + # get pages + $pages = Get-PodeWebPage -Name $Name -Group $Group -NoGroup:$NoGroup + + # are there any pages? + if ($null -eq $pages) { + return $false + } + + return (@($pages) | Measure-Object).Count -gt 0 + } +} + +function New-PodeWebPageGroup { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter()] [string] $Name, [Parameter()] [string] - $Group, + $DisplayName, [Parameter()] + [string] + $Icon, + [switch] - $NoGroup + $NoCounter, + + [switch] + $Hide, + + [switch] + $PassThru + ) + + # test if page group exists + if (Test-PodeWebPageGroup -Name $Name) { + throw "Page Group already exists: $($Name)" + } + + # set display name + if ([string]::IsNullOrEmpty($DisplayName)) { + $DisplayName = $Name + } + + # setup group meta + $groupMeta = @{ + Operation = 'New' + ComponentType = 'Group' + ObjectType = 'Group' + ID = Get-PodeWebRandomName + Name = $Name + DisplayName = [System.Net.WebUtility]::HtmlEncode($DisplayName) + Icon = $Icon + NoCounter = $NoCounter.IsPresent + Hide = $Hide.IsPresent + Pages = @{} + } + + # add group meta to state + $groups = Get-PodeWebState -Name 'groups' + $groups[$Name] = $groupMeta + + if ($PassThru) { + return $groupMeta + } +} + +function Get-PodeWebPageGroup { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Name = $null ) - # get pages - $pages = Get-PodeWebPage -Name $Name -Group $Group -NoGroup:$NoGroup + $groups = Get-PodeWebState -Name 'groups' - # are there any pages? - if ($null -eq $pages) { - return $false + # get all groups on null + if ($null -eq $Name) { + return $groups } - return (@($pages) | Measure-Object).Count -gt 0 + # return specific group + return $groups[$Name] +} + +function Test-PodeWebPageGroup { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Name + ) + + return (Get-PodeWebState -Name 'groups').ContainsKey($Name) +} + +function Remove-PodeWebPageGroup { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Name + ) + + $null = (Get-PodeWebState -Name 'groups').Remove($Name) }