initial commit
|
@ -0,0 +1,9 @@
|
|||
# Microsoft Open Source Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
|
||||
Resources:
|
||||
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
|
|
@ -0,0 +1,23 @@
|
|||
Microsoft.FactoryOrchestrator
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
|
@ -0,0 +1,54 @@
|
|||
| CI Build |
|
||||
|----|
|
||||
| [![Build Status](https://microsoft.visualstudio.com/OneCore/_apis/build/status/FactoryOrchestrator/FO-PublicFacing-CI?branchName=master)](https://microsoft.visualstudio.com/OneCore/_build/latest?definitionId=54749&branchName=main) |
|
||||
|
||||
# Introduction
|
||||
Factory Orchestrator is a tool for built for Original Equipment Maufacturers to aid in the manufacturing of
|
||||
Factory Orchestrator consists of the following projects
|
||||
## 1. FactoryOrchestratorCoreLibrary
|
||||
.NET Standard library containing the core FactoryOrchestrator classes. Required in all projects.
|
||||
## 2. FactoryOrchestratorServerLibrary
|
||||
A .NET Standard library containing the server-side FactoryOrchestrator classes. Required on all FactoryOrchestrator server projects.
|
||||
## 3. FactoryOrchestratorClientLibrary
|
||||
.NET Standard library containing the client-side FactoryOrchestrator classes. Has helper classes which are optional for all FactoryOrchestrator client projects.
|
||||
## 4. FactoryOrchestratorService
|
||||
.NET Core Executable project for FactoryOrchestratorService.exe, the FactoryOrchestrator server implementation.
|
||||
## 5. FactoryOrchestratorApp
|
||||
.NET UWP app project for FactoryOrchestratorApp.exe, the UWP used to communicate with FactoryOrchestratorService and run UI tests.
|
||||
## Open Source Component
|
||||
### IpcServiceFramework
|
||||
https://github.com/jacqueskang/IpcServiceFramework
|
||||
FactoryOrchestrator forks the source of IpcServiceFramework. At this time, we are actively working on integrating our version of IpcServiceFramework the official IpcServiceFramework repo, removing the need for this fork.
|
||||
|
||||
# Contributing
|
||||
|
||||
## 1. Accept Contributor Licence Agreement (CLA)
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
## 2. Clone Project
|
||||
Visual Studio is recommended for developing
|
||||
|
||||
## 3. Address Unsigned Powershell Scripts
|
||||
FactoryOrchestrator contains a series of unsigned powershell scripts. Windows security measures prevent unsigned scripts from executing. In order to develop on FactoryOrchestrator, you need to do one of two things.
|
||||
|
||||
### Self-sign
|
||||
More information on how to do this can be found here: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_signing?view=powershell-7
|
||||
|
||||
### Set the Execution Policy to Unrestricted
|
||||
This method is not recommended, but is doable. Setting the Execution Policy to Unresricted allows any powershell script to run.
|
||||
Documentation on Execution Policy:
|
||||
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-7
|
||||
|
||||
# Open Source Software
|
||||
IpcServiceFramework - Jacques Kang - [MIT License](https://github.com/jacqueskang/IpcServiceFramework/blob/develop/LICENSE)
|
||||
DotNetCore.WindowsService - Peter Kottas - [MIT License](https://github.com/PeterKottas/DotNetCore.WindowsService/blob/master/LICENSE)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.5 BLOCK -->
|
||||
|
||||
## Security
|
||||
|
||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
|
||||
|
||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
|
||||
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
@ -0,0 +1,82 @@
|
|||
|
||||
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
pool:
|
||||
name: Hosted Windows 2019 with VS2019
|
||||
demands:
|
||||
- msbuild
|
||||
- visualstudio
|
||||
|
||||
#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971
|
||||
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- '*' # must quote since "*" is a YAML reserved character; we want a string
|
||||
jobs:
|
||||
- job: BuildFactoryOrchestrator
|
||||
steps:
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: 'NuGet restore'
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln Any CPU'
|
||||
inputs:
|
||||
platform: 'Any CPU'
|
||||
configuration: 'Debug'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln x86 and all arch AppXPackage'
|
||||
inputs:
|
||||
platform: x86
|
||||
configuration: 'Debug'
|
||||
msbuildArgs: '/p:AppxBundlePlatforms="x86|x64|arm" /p:AppxPackageDir="$(build.artifactStagingDirectory)\AppxPackages\\" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=Sideload'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln x64'
|
||||
inputs:
|
||||
platform: x64
|
||||
configuration: 'Debug'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln ARM'
|
||||
inputs:
|
||||
platform: ARM
|
||||
configuration: 'Debug'
|
||||
|
||||
- job: RunPoliCheck
|
||||
steps:
|
||||
- task: PoliCheck@1
|
||||
displayName: 'Run Policheck'
|
||||
inputs:
|
||||
inputType: 'Basic'
|
||||
targetType: 'F'
|
||||
targetArgument: '$(Build.SourcesDirectory)'
|
||||
result: 'PoliCheck.xml'
|
||||
#d:\a\1\_sdt\logs\PoliCheck\PoliCheck.xml
|
||||
#copying all from _sdt directory "data folder"
|
||||
- task: CopyFiles@2
|
||||
inputs:
|
||||
SourceFolder: '$(Agent.BuildDirectory)\_sdt\logs\PoliCheck' #d:\a\1\_sdt
|
||||
contents: '**'
|
||||
targetFolder: $(Build.ArtifactStagingDirectory)
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
artifactName: BuildArtifactStagingDirectoryPoli
|
||||
|
||||
- job: RunFxCop
|
||||
steps:
|
||||
- task: FxCop@2
|
||||
inputs:
|
||||
inputType: 'Basic'
|
||||
targets: '**\*.cs'
|
||||
- task: CopyFiles@2
|
||||
inputs:
|
||||
SourceFolder: '$(Agent.BuildDirectory)\_sdt\logs\FxCop'
|
||||
contents: '**'
|
||||
targetFolder: $(Build.ArtifactStagingDirectory)
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
artifactName: BuildArtifactStagingDirectoryFx
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
pool:
|
||||
name: Hosted Windows 2019 with VS2019
|
||||
demands:
|
||||
- msbuild
|
||||
- visualstudio
|
||||
|
||||
#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971
|
||||
|
||||
trigger:
|
||||
- master
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
- template: ./templates/template-build-all-arch.yaml
|
|
@ -0,0 +1,18 @@
|
|||
#CI Pipeline. Triggers every merge attempt with main branch
|
||||
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
pool:
|
||||
name: Hosted Windows 2019 with VS2019
|
||||
demands:
|
||||
- msbuild
|
||||
- visualstudio
|
||||
|
||||
#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971
|
||||
|
||||
trigger:
|
||||
- master
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
- template: ./templates/template-build-all-arch.yaml
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
pool:
|
||||
name: Hosted Windows 2019 with VS2019
|
||||
demands:
|
||||
- msbuild
|
||||
- visualstudio
|
||||
|
||||
jobs:
|
||||
- job: BuildFactoryOrchestrator
|
||||
steps:
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: 'NuGet restore'
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln Any CPU'
|
||||
inputs:
|
||||
platform: 'Any CPU'
|
||||
configuration: 'Debug'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln x86 and all arch AppXPackage'
|
||||
inputs:
|
||||
platform: x86
|
||||
configuration: 'Debug'
|
||||
msbuildArgs: '/p:AppxBundlePlatforms="x86|x64|arm" /p:AppxPackageDir="$(build.artifactStagingDirectory)\AppxPackages\\" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=Sideload'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln x64'
|
||||
inputs:
|
||||
platform: x64
|
||||
configuration: 'Debug'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\*.sln ARM'
|
||||
inputs:
|
||||
platform: ARM
|
||||
configuration: 'Debug'
|
||||
|
||||
- job: RunPoliCheck
|
||||
steps:
|
||||
- task: PoliCheck@1
|
||||
displayName: 'Run Policheck'
|
||||
inputs:
|
||||
inputType: 'Basic'
|
||||
targetType: 'F'
|
||||
targetArgument: '$(Build.SourcesDirectory)'
|
||||
result: 'PoliCheck.xml'
|
||||
#d:\a\1\_sdt\logs\PoliCheck\PoliCheck.xml
|
||||
#copying all from _sdt directory "data folder"
|
||||
- task: CopyFiles@2
|
||||
inputs:
|
||||
SourceFolder: '$(Agent.BuildDirectory)\_sdt\logs\PoliCheck' #d:\a\1\_sdt
|
||||
contents: '**'
|
||||
targetFolder: $(Build.ArtifactStagingDirectory)
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
artifactName: BuildArtifactStagingDirectoryPoli
|
||||
|
||||
- job: RunFxCop
|
||||
steps:
|
||||
- task: FxCop@2
|
||||
inputs:
|
||||
inputType: 'Basic'
|
||||
targets: '**\*.cs'
|
||||
- task: CopyFiles@2
|
||||
inputs:
|
||||
SourceFolder: '$(Agent.BuildDirectory)\_sdt\logs\FxCop'
|
||||
contents: '**'
|
||||
targetFolder: $(Build.ArtifactStagingDirectory)
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
artifactName: BuildArtifactStagingDirectoryFx
|
|
@ -0,0 +1,334 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
.vscode/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# AssemblyInfo is auto-modified
|
||||
**/Properties/AssemblyInfo.cs
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
|
@ -0,0 +1,53 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.AboutPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Button x:Name="BackButton" Grid.Row="0" VerticalAlignment="Top" Click="Back_Click" Style="{StaticResource NavigationBackButtonNormalStyle}" Visibility="Collapsed" AutomationProperties.Name="Go Back"/>
|
||||
<StackPanel Orientation="Vertical" Grid.Row="0" Margin="5">
|
||||
<TextBlock Text="Factory Orchestrator" Style="{StaticResource HeaderTextBlockStyle}" HorizontalAlignment="Center"/>
|
||||
<TextBlock x:Name="AppVersionText" Style="{StaticResource SubheaderTextBlockStyle}" HorizontalAlignment="Center"/>
|
||||
<TextBlock x:Name="ServiceVersionText" Style="{StaticResource SubheaderTextBlockStyle}" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="" Style="{StaticResource SubheaderTextBlockStyle}" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="(c) Microsoft Corporation" Style="{StaticResource SubheaderTextBlockStyle}" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" Grid.Row="2" Margin="5">
|
||||
<TextBlock Text="Open Source Software" HorizontalAlignment="Center" Style="{StaticResource SubheaderTextBlockStyle}"/>
|
||||
<TextBlock Text="IpcServiceFramework - Jacques Kang - MIT License - https://github.com/jacqueskang/IpcServiceFramework" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="DotNetCore.WindowsService - Peter Kottas - MIT License - https://github.com/PeterKottas/DotNetCore.WindowsService" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="WindowsDevicePortalWrapper - Microsoft Corporation and mgurlitz - MIT License - https://github.com/mgurlitz/WindowsDevicePortalWrapper/tree/feat-standard" HorizontalAlignment="Left"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" Grid.Row="3" Margin="5">
|
||||
<TextBlock Text="MIT License for IpcServiceFramework, DotNetCore.WindowsService, and WindowsDevicePortalWrapper" Style="{StaticResource SubtitleTextBlockStyle}" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the Software), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:" TextWrapping="WrapWholeWords" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="" TextWrapping="WrapWholeWords" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software." TextWrapping="WrapWholeWords" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="" TextWrapping="WrapWholeWords" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE." TextWrapping="WrapWholeWords" HorizontalAlignment="Left"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class AboutPage : Page
|
||||
{
|
||||
public AboutPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyVersion = assembly.GetName().Version.ToString();
|
||||
object[] attributes = assembly.GetCustomAttributes(true);
|
||||
|
||||
string description = "";
|
||||
|
||||
var descrAttr = attributes.OfType<AssemblyDescriptionAttribute>().FirstOrDefault();
|
||||
if (descrAttr != null)
|
||||
{
|
||||
description = descrAttr.Description;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
description = "Debug" + description;
|
||||
#endif
|
||||
|
||||
AppVersionText.Text = $"App Version: {assemblyVersion} ({description})";
|
||||
}
|
||||
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ServiceVersionText.Text = "Service Version: ";
|
||||
ServiceVersionText.Text += await Client.GetServiceVersionString();
|
||||
}
|
||||
catch (FactoryOrchestratorConnectionException)
|
||||
{
|
||||
// Just ignore it
|
||||
}
|
||||
}
|
||||
|
||||
if (this.Frame.BackStack.First().SourcePageType == typeof(ConnectionPage))
|
||||
{
|
||||
BackButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
private void Back_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
On_BackRequested();
|
||||
}
|
||||
|
||||
private bool On_BackRequested()
|
||||
{
|
||||
if (this.Frame.CanGoBack)
|
||||
{
|
||||
this.Frame.GoBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Application
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP">
|
||||
|
||||
</Application>
|
|
@ -0,0 +1,607 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.ApplicationModel.ExtendedExecution;
|
||||
using Windows.Foundation;
|
||||
using Windows.Management.Deployment;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Core.Preview;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
sealed partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Suspending += OnSuspending;
|
||||
this.UnhandledException += UnhandledExceptionHandler;
|
||||
MainPageLastNavTag = null;
|
||||
RunWaitingForResult = null;
|
||||
Client = null;
|
||||
connectionFailureSem = new SemaphoreSlim(1,1);
|
||||
pollingFailureSem = new SemaphoreSlim(1,1);
|
||||
IsServiceExecutingBootTasks = true;
|
||||
IgnoreVersionMismatch = false;
|
||||
OnConnectionPage = true;
|
||||
}
|
||||
|
||||
private void UnhandledExceptionHandler(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var exception = e.Exception;
|
||||
if (exception.GetType() == typeof(FactoryOrchestratorConnectionException))
|
||||
{
|
||||
e.Handled = true;
|
||||
OnConnectionFailure();
|
||||
}
|
||||
else if (exception.GetType() == typeof(FactoryOrchestratorVersionMismatchException))
|
||||
{
|
||||
e.Handled = true;
|
||||
_ = OnVersionMismatchFailure((FactoryOrchestratorVersionMismatchException)exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Handled = false;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(exception);
|
||||
}
|
||||
|
||||
private Frame PreLaunchSetUp()
|
||||
{
|
||||
Frame rootFrame = Window.Current.Content as Frame;
|
||||
|
||||
extendedExecution = new ExtendedExecutionSession();
|
||||
extendedExecution.Reason = ExtendedExecutionReason.Unspecified;
|
||||
_ = extendedExecution.RequestExtensionAsync();
|
||||
|
||||
if (rootFrame == null)
|
||||
{
|
||||
// Create a Frame to act as the navigation context and navigate to the first page
|
||||
rootFrame = new Frame();
|
||||
rootFrame.NavigationFailed += OnNavigationFailed;
|
||||
// Place the frame in the current Window
|
||||
Window.Current.Content = rootFrame;
|
||||
Window.Current.Activate();
|
||||
}
|
||||
|
||||
rootFrame.CacheSize = 4;
|
||||
// Requires confirmAppClose restricted capability
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += App_CloseRequestedAsync;
|
||||
return rootFrame;
|
||||
}
|
||||
|
||||
protected override async void OnActivated(IActivatedEventArgs args)
|
||||
{
|
||||
if (args.Kind == ActivationKind.Protocol)
|
||||
{
|
||||
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
|
||||
var path = eventArgs.Uri.LocalPath;
|
||||
Frame rootFrame = PreLaunchSetUp();
|
||||
if (Client != null)
|
||||
{
|
||||
if (await Client.TryConnect(IgnoreVersionMismatch))
|
||||
{
|
||||
OnConnectionPage = false;
|
||||
rootFrame.Navigate(typeof(MainPage), path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
Client = new FactoryOrchestratorUWPClient(IPAddress.Loopback, 45684);
|
||||
Client.OnConnected += OnIpcConnected;
|
||||
|
||||
if (await Client.TryConnect(IgnoreVersionMismatch))
|
||||
{
|
||||
OnConnectionPage = false;
|
||||
rootFrame.Navigate(typeof(MainPage), path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Client = null;
|
||||
rootFrame.Navigate(typeof(ConnectionPage), path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal async void OnServerPollerException(object source, ServerPollerExceptionHandlerArgs e)
|
||||
{
|
||||
var poller = source as ServerPoller;
|
||||
|
||||
if (e.Exception.GetType() == typeof(FactoryOrchestratorConnectionException))
|
||||
{
|
||||
OnConnectionFailure();
|
||||
}
|
||||
else if (poller.IsPolling)
|
||||
{
|
||||
pollingFailureSem.Wait();
|
||||
try
|
||||
{
|
||||
if (e.Exception.GetType() == typeof(FactoryOrchestratorUnkownGuidException))
|
||||
{
|
||||
// Service was likely restarted or the guid was deleted via another Client, stop polling it
|
||||
poller.StopPolling();
|
||||
}
|
||||
|
||||
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
|
||||
{
|
||||
var stopPollBox = new CheckBox()
|
||||
{
|
||||
IsChecked = false,
|
||||
Content = "Stop polling this object?"
|
||||
};
|
||||
|
||||
var dialogStack = new StackPanel();
|
||||
dialogStack.Orientation = Orientation.Vertical;
|
||||
dialogStack.Children.Add(new TextBlock()
|
||||
{
|
||||
Text = e.Exception.Message + "\r\n\r\nThis can occur when the Factory Orchestrator Service is restarted during an operation",
|
||||
TextWrapping = TextWrapping.WrapWholeWords
|
||||
});
|
||||
|
||||
if (poller.IsPolling)
|
||||
{
|
||||
dialogStack.Children.Add(stopPollBox);
|
||||
}
|
||||
|
||||
ContentDialog errorDialog = new ContentDialog()
|
||||
{
|
||||
Title = "Polling Exception",
|
||||
Content = dialogStack,
|
||||
CloseButtonText = $"Continue"
|
||||
};
|
||||
try
|
||||
{
|
||||
await errorDialog.ShowAsync();
|
||||
if (stopPollBox.IsChecked == true)
|
||||
{
|
||||
poller.StopPolling();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Bug: i think this doesnt work ;)
|
||||
// System.Exception is thrown if there is already a ContentDialog visible on the screen. Just ignore it
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
pollingFailureSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(e.Exception);
|
||||
}
|
||||
|
||||
public async void OnConnectionFailure()
|
||||
{
|
||||
connectionFailureSem.Wait();
|
||||
try
|
||||
{
|
||||
if (!OnConnectionPage && !Client.IsConnected)
|
||||
{
|
||||
IAsyncOperation<ContentDialogResult> resultTask = null;
|
||||
|
||||
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
StackPanel s = new StackPanel();
|
||||
s.Children.Add(
|
||||
new TextBlock()
|
||||
{
|
||||
Text = "Cannot reach Factory Orchestrator Service!" + Environment.NewLine + "Attempting to reconnect...",
|
||||
Margin = new Thickness(10)
|
||||
});
|
||||
|
||||
s.Children.Add(
|
||||
new ProgressBar()
|
||||
{
|
||||
IsIndeterminate = true
|
||||
});
|
||||
|
||||
ContentDialog errorDialog = new ContentDialog()
|
||||
{
|
||||
Title = "Communication Error",
|
||||
Content = s,
|
||||
CloseButtonText = $"Disconnect from {Client.IpAddress}"
|
||||
};
|
||||
|
||||
resultTask = errorDialog.ShowAsync();
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
while (!await Client.TryConnect(IgnoreVersionMismatch))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Waiting for connection...");
|
||||
if (resultTask.Status == AsyncStatus.Completed)
|
||||
{
|
||||
OnConnectionPage = true;
|
||||
|
||||
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
var frame = Window.Current.Content as Frame;
|
||||
frame.Navigate(typeof(ConnectionPage));
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FactoryOrchestratorVersionMismatchException ex)
|
||||
{
|
||||
// The version was matched before, now it isn't. The client device likely changed.
|
||||
resultTask.Cancel();
|
||||
// Wait to ensure dialog boxes don't collide and cause crash
|
||||
await Task.Delay(100);
|
||||
await OnVersionMismatchFailure(ex, false);
|
||||
if (IgnoreVersionMismatch)
|
||||
{
|
||||
await Client.TryConnect(IgnoreVersionMismatch);
|
||||
}
|
||||
}
|
||||
|
||||
resultTask.Cancel();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectionFailureSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception handler called when Client and Service versions are mismatched.
|
||||
/// </summary>
|
||||
/// <param name="e">The exception.</param>
|
||||
/// <param name="navigateToConnectionPage">If set to <c>true</c> navigate to connection page if the user chooses to not exit.</param>
|
||||
/// <returns></returns>
|
||||
public async Task OnVersionMismatchFailure(FactoryOrchestratorVersionMismatchException e, bool navigateToConnectionPage = true)
|
||||
{
|
||||
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
|
||||
{
|
||||
ContentDialog errorDialog = new ContentDialog()
|
||||
{
|
||||
Title = "Client-Service Version Mismatch Error",
|
||||
Content = $"The Client and Service major versions must match!\n" +
|
||||
$"Client Version: {e.ClientVersion}\n" +
|
||||
$"Service Version: {e.ServiceVersion}",
|
||||
CloseButtonText = $"Exit",
|
||||
PrimaryButtonText = $"Continue (not recommended)",
|
||||
};
|
||||
|
||||
var result = await errorDialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
IgnoreVersionMismatch = true;
|
||||
if (navigateToConnectionPage)
|
||||
{
|
||||
((Frame)(Window.Current.Content)).Navigate(typeof(ConnectionPage));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Exit();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the property to be set. Otherwise the user chose to exit.
|
||||
while (IgnoreVersionMismatch == false)
|
||||
{
|
||||
await Task.Delay(50);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// </summary>
|
||||
/// <param name="e">Details about the launch request and process.</param>
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs e)
|
||||
{
|
||||
Frame rootFrame = PreLaunchSetUp();
|
||||
|
||||
if (e.PrelaunchActivated == false)
|
||||
{
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
Client = new FactoryOrchestratorUWPClient(IPAddress.Loopback, 45684);
|
||||
Client.OnConnected += OnIpcConnected;
|
||||
|
||||
if (await Client.TryConnect(IgnoreVersionMismatch))
|
||||
{
|
||||
OnConnectionPage = false;
|
||||
rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When the navigation stack isn't restored navigate to the first page,
|
||||
// configuring the new page by passing required information as a navigation
|
||||
// parameter
|
||||
Client = null;
|
||||
rootFrame.Navigate(typeof(ConnectionPage), e.Arguments);
|
||||
}
|
||||
}
|
||||
// Ensure the current window is active
|
||||
Window.Current.Activate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ask the user to confirm before quitting, as on Factory it might not be easy to relaunch the app
|
||||
private async void App_CloseRequestedAsync(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||
{
|
||||
var deferral = e.GetDeferral();
|
||||
|
||||
ContentDialog exitFlyout = new ContentDialog()
|
||||
{
|
||||
Title = "Exit?",
|
||||
Content = "Exit Factory Orchestrator?",
|
||||
CloseButtonText = "No",
|
||||
PrimaryButtonText = "Yes",
|
||||
};
|
||||
|
||||
var result = await exitFlyout.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.None)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when Navigation to a certain page fails
|
||||
/// </summary>
|
||||
/// <param name="sender">The Frame which failed navigation</param>
|
||||
/// <param name="e">Details about the navigation failure</param>
|
||||
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when application execution is being suspended. Application state is saved
|
||||
/// without knowing whether the application will be terminated or resumed with the contents
|
||||
/// of memory still intact.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the suspend request.</param>
|
||||
/// <param name="e">Details about the suspend request.</param>
|
||||
private void OnSuspending(object sender, SuspendingEventArgs e)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
//TODO: Save application state and stop any background activity
|
||||
deferral.Complete();
|
||||
}
|
||||
|
||||
public void OnIpcConnected()
|
||||
{
|
||||
// disable so this doesnt fire again
|
||||
Client.OnConnected -= OnIpcConnected;
|
||||
|
||||
lock (onConnectionLock)
|
||||
{
|
||||
if (Client.IpAddress == lastIp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (lastIp != null)
|
||||
{
|
||||
// We connected to a new device
|
||||
lastIp = Client.IpAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
// First connection, do setup. Only runs once!
|
||||
lastIp = Client.IpAddress;
|
||||
|
||||
// One thread queues events, another dequeues and handles them.
|
||||
// Poll every 1 second
|
||||
// TODO: Only start these tasks once, so we can handle new IPC connection correctly. Likely need state cleanup too.
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await CheckForServiceEvents();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await HandleServiceEvents();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckForServiceEvents()
|
||||
{
|
||||
List<ServiceEvent> newEvents = new List<ServiceEvent>();
|
||||
|
||||
try
|
||||
{
|
||||
if (!eventSeen)
|
||||
{
|
||||
newEvents = await Client.GetServiceEvents();
|
||||
}
|
||||
else
|
||||
{
|
||||
newEvents = await Client.GetServiceEvents(lastEventIndex);
|
||||
}
|
||||
}
|
||||
catch (FactoryOrchestratorConnectionException)
|
||||
{
|
||||
// We might change device or the system might have rebooted, start over
|
||||
eventSeen = false;
|
||||
OnConnectionFailure();
|
||||
while (OnConnectionPage || (!Client.IsConnected))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle events in a queue
|
||||
if (newEvents.Count > 0)
|
||||
{
|
||||
eventSeen = true;
|
||||
lastEventIndex = newEvents[newEvents.Count - 1].EventIndex;
|
||||
|
||||
foreach (var evnt in newEvents)
|
||||
{
|
||||
serviceEventQueue.Enqueue(evnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleServiceEvents()
|
||||
{
|
||||
// Handle one event at a time, oldest first
|
||||
ServiceEvent evnt;
|
||||
while (serviceEventQueue.TryDequeue(out evnt))
|
||||
{
|
||||
switch(evnt.ServiceEventType)
|
||||
{
|
||||
case ServiceEventType.ServiceStart:
|
||||
IsServiceExecutingBootTasks = true;
|
||||
OnServiceStart?.Invoke();
|
||||
break;
|
||||
case ServiceEventType.BootTasksComplete:
|
||||
IsServiceExecutingBootTasks = false;
|
||||
OnServiceDoneExecutingBootTasks?.Invoke();
|
||||
break;
|
||||
case ServiceEventType.WaitingForExternalTaskRun:
|
||||
// Check if we are localhost, if so we are the DUT and need to run the UWP task for the server.
|
||||
// If not, do nothing, as we are not the DUT.
|
||||
if (Client.IsLocalHost)
|
||||
{
|
||||
// TODO: Performance: this should be in its own thread, so other service events can be handled
|
||||
// Only allow one external run at a time
|
||||
TaskRun run = null;
|
||||
|
||||
while (run == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
run = await Client.QueryTaskRun((Guid)evnt.Guid);
|
||||
}
|
||||
catch (FactoryOrchestratorUnkownGuidException)
|
||||
{
|
||||
// Run is no longer valid, ignore it
|
||||
return;
|
||||
}
|
||||
catch (FactoryOrchestratorConnectionException)
|
||||
{
|
||||
OnConnectionFailure();
|
||||
while ((OnConnectionPage) || (!Client.IsConnected))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!run.TaskRunComplete)
|
||||
{
|
||||
await HandleExternalTaskRunAsync(run);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleExternalTaskRunAsync(TaskRun run)
|
||||
{
|
||||
RunWaitingForResult = run;
|
||||
|
||||
// Navigate to result page
|
||||
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
((Frame)Window.Current.Content).Navigate(typeof(ExternalTestResultPage));
|
||||
});
|
||||
|
||||
// TODO: Performance: Use signaling
|
||||
// Block from handing a new system event until the current one is handled
|
||||
// This is set by ExternalTestResultPage
|
||||
while (!RunWaitingForResult.TaskRunComplete)
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
RunWaitingForResult = null;
|
||||
}
|
||||
|
||||
public TaskRun RunWaitingForResult { get; private set; }
|
||||
public string MainPageLastNavTag { get; set; }
|
||||
public FactoryOrchestratorUWPClient Client { get; set; }
|
||||
public bool OnConnectionPage { get; set; }
|
||||
public bool IgnoreVersionMismatch { get; set; }
|
||||
/// <summary>
|
||||
/// <c>true if Service is executing boot TaskLists.</c>
|
||||
/// </summary>
|
||||
public bool IsServiceExecutingBootTasks { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the Service is done executing boot tasks.
|
||||
/// </summary>
|
||||
public event ServiceDoneExecutingBootTasks OnServiceDoneExecutingBootTasks;
|
||||
/// <summary>
|
||||
/// Event raised when the Service is starting and is executing boot tasks.
|
||||
/// </summary>
|
||||
public event ServiceDoneExecutingBootTasks OnServiceStart;
|
||||
|
||||
private SemaphoreSlim connectionFailureSem;
|
||||
private SemaphoreSlim pollingFailureSem;
|
||||
private bool eventSeen = false;
|
||||
private ulong lastEventIndex;
|
||||
private ConcurrentQueue<ServiceEvent> serviceEventQueue = new ConcurrentQueue<ServiceEvent>();
|
||||
private ExtendedExecutionSession extendedExecution = null;
|
||||
private object onConnectionLock = new object();
|
||||
private IPAddress lastIp = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature for OnServiceDoneExecutingBootTasks.
|
||||
/// </summary>
|
||||
public delegate void ServiceDoneExecutingBootTasks();
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.AppsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid x:Name="AppsGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Text="Launch installed app package (exit with ALT+F4):" Style="{StaticResource SubtitleTextBlockStyle}"/>
|
||||
<ListView x:Name="PackageList" Grid.Row="1" ScrollViewer.VerticalScrollMode="Enabled" ScrollViewer.VerticalScrollBarVisibility="Auto" IsItemClickEnabled="True" SelectionMode="Single" ItemClick="PackageList_ItemClick"/>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Management.Deployment;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class AppsPage : Page
|
||||
{
|
||||
public AppsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
PackageStrings = new List<string>();
|
||||
}
|
||||
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
// Get installed UWPs
|
||||
try
|
||||
{
|
||||
PackageStrings = await Client.GetInstalledApps();
|
||||
PackageList.ItemsSource = PackageStrings;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ContentDialog failedAppsDialog = new ContentDialog
|
||||
{
|
||||
Title = "Failed to query apps",
|
||||
Content = ex.Message,
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await failedAppsDialog.ShowAsync();
|
||||
}
|
||||
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
private async void PackageList_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
await Client.RunApp((string)e.ClickedItem);
|
||||
}
|
||||
|
||||
public List<string> PackageStrings { get; private set; }
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 4.4 KiB |
После Ширина: | Высота: | Размер: 5.6 KiB |
После Ширина: | Высота: | Размер: 7.0 KiB |
После Ширина: | Высота: | Размер: 11 KiB |
После Ширина: | Высота: | Размер: 32 KiB |
После Ширина: | Высота: | Размер: 1.4 KiB |
После Ширина: | Высота: | Размер: 1.7 KiB |
После Ширина: | Высота: | Размер: 1.9 KiB |
После Ширина: | Высота: | Размер: 2.6 KiB |
После Ширина: | Высота: | Размер: 5.6 KiB |
После Ширина: | Высота: | Размер: 4.8 KiB |
После Ширина: | Высота: | Размер: 5.9 KiB |
После Ширина: | Высота: | Размер: 7.8 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 37 KiB |
После Ширина: | Высота: | Размер: 2.1 KiB |
После Ширина: | Высота: | Размер: 2.5 KiB |
После Ширина: | Высота: | Размер: 3.1 KiB |
После Ширина: | Высота: | Размер: 4.3 KiB |
После Ширина: | Высота: | Размер: 11 KiB |
После Ширина: | Высота: | Размер: 617 B |
После Ширина: | Высота: | Размер: 11 KiB |
После Ширина: | Высота: | Размер: 1.0 KiB |
После Ширина: | Высота: | Размер: 1.5 KiB |
После Ширина: | Высота: | Размер: 1.1 KiB |
После Ширина: | Высота: | Размер: 1.4 KiB |
После Ширина: | Высота: | Размер: 1.6 KiB |
После Ширина: | Высота: | Размер: 2.2 KiB |
После Ширина: | Высота: | Размер: 4.5 KiB |
После Ширина: | Высота: | Размер: 518 B |
После Ширина: | Высота: | Размер: 712 B |
После Ширина: | Высота: | Размер: 1.2 KiB |
После Ширина: | Высота: | Размер: 7.7 KiB |
После Ширина: | Высота: | Размер: 904 B |
После Ширина: | Высота: | Размер: 1.2 KiB |
После Ширина: | Высота: | Размер: 1.5 KiB |
После Ширина: | Высота: | Размер: 1.9 KiB |
После Ширина: | Высота: | Размер: 2.3 KiB |
После Ширина: | Высота: | Размер: 3.1 KiB |
После Ширина: | Высота: | Размер: 7.7 KiB |
После Ширина: | Высота: | Размер: 2.2 KiB |
После Ширина: | Высота: | Размер: 2.7 KiB |
После Ширина: | Высота: | Размер: 3.3 KiB |
После Ширина: | Высота: | Размер: 4.8 KiB |
После Ширина: | Высота: | Размер: 12 KiB |
После Ширина: | Высота: | Размер: 3.2 KiB |
|
@ -0,0 +1,85 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.ConnectionPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid>
|
||||
<Button x:Name="ExitButton" VerticalAlignment="Top" HorizontalAlignment="Right">
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" FontSize="30" VerticalAlignment="Center" />
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="ExitFlyout">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Text="Exit Factory Orchestrator?" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button x:Name="ConfirmExit" Padding="5" Margin="5" Click="ConfirmExit_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock Padding="5,0" TextWrapping="Wrap" Text="Exit"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" Margin="50,50,50,50">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="20"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="20"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="99" Text="Welcome to Factory Orchestrator!" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource HeaderTextBlockStyle}"/>
|
||||
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="99" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="99" Text="Attempting to connect to your local device" HorizontalAlignment="Center" VerticalAlignment="Top" Style="{StaticResource SubheaderTextBlockStyle}"/>
|
||||
<ProgressBar x:Name="ConnectingProgress" Grid.Row="3" Grid.Column="1" IsIndeterminate="True" Visibility="Visible"/>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="99" Text="Or, please enter the IPv4 address of the device you wish to connect to:" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource TitleTextBlockStyle}"/>
|
||||
<Grid Grid.Row="6" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Text="IP Address:" x:Name="IpText"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" x:Name="IpTextBox" Text="" Margin="5" Width="150" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="NoWrap" Visibility="Visible" TextAlignment="Center" TextChanged="IpTextBox_TextChanged" InputScope="Number" KeyDown="IpTextBox_KeyDown" AutomationProperties.LabeledBy="{Binding ElementName=IpText}"/>
|
||||
<Button Grid.Row="0" Grid.Column="4" x:Name="ConnectButton" Margin="1" VerticalAlignment="Center" Content="Connect" Click="ConnectButton_Click" IsEnabled="False"/>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="9" HorizontalAlignment="Right">
|
||||
<Button x:Name="ValidateXMLButton" Content="Validate FactoryOrchestratorXML" Margin="5" Click="ValidateXMLButton_Click"/>
|
||||
<Button x:Name="AboutButton" Content="About Factory Orchestrator" Margin="5" Click="AboutButton_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class ConnectionPage : Page
|
||||
{
|
||||
public ConnectionPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
((App)Application.Current).Client = new FactoryOrchestratorUWPClient(IPAddress.Loopback, 45684);
|
||||
((App)Application.Current).Client.OnConnected += ((App)Application.Current).OnIpcConnected;
|
||||
((App)Application.Current).OnConnectionPage = true;
|
||||
connectionSem = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
lastNavTag = e.Parameter as string;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// Attempt to connect to localhost every 2 seconds in a background task
|
||||
while (!((App)Application.Current).Client.IsConnected)
|
||||
{
|
||||
await connectionSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
// Ensure we are not connected, user connection might have succeeded while waiting for semaphore
|
||||
if (!((App)Application.Current).Client.IsConnected)
|
||||
{
|
||||
if (((App)Application.Current).Client.IpAddress != IPAddress.Loopback)
|
||||
{
|
||||
// User connection attempted and failed. Recreate Client
|
||||
((App)Application.Current).Client = new FactoryOrchestratorUWPClient(IPAddress.Loopback, 45684);
|
||||
((App)Application.Current).Client.OnConnected += ((App)Application.Current).OnIpcConnected;
|
||||
}
|
||||
|
||||
if (await ((App)Application.Current).Client.TryConnect(((App)Application.Current).IgnoreVersionMismatch))
|
||||
{
|
||||
((App)Application.Current).OnConnectionPage = false;
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
this.Frame.Navigate(typeof(MainPage), lastNavTag);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectionSem.Release();
|
||||
}
|
||||
|
||||
if (!((App)Application.Current).Client.IsConnected)
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void ConnectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IPAddress ip = null;
|
||||
bool validIp = false;
|
||||
|
||||
validIp = IPAddress.TryParse(IpTextBox.Text, out ip);
|
||||
|
||||
if (validIp)
|
||||
{
|
||||
ConnectButton.IsEnabled = false;
|
||||
await connectionSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
((App)Application.Current).Client = new FactoryOrchestratorUWPClient(ip, 45684);
|
||||
((App)Application.Current).Client.OnConnected += ((App)Application.Current).OnIpcConnected;
|
||||
if (await ((App)Application.Current).Client.TryConnect(((App)Application.Current).IgnoreVersionMismatch))
|
||||
{
|
||||
((App)Application.Current).OnConnectionPage = false;
|
||||
this.Frame.Navigate(typeof(MainPage), lastNavTag);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowConnectFailure(ip.ToString());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectionSem.Release();
|
||||
ConnectButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ShowConnectFailure(string ipStr)
|
||||
{
|
||||
ContentDialog failedConnectDialog = new ContentDialog
|
||||
{
|
||||
Title = "Unable to connect to target IP",
|
||||
Content = $"Could not connect to {ipStr}.\n\nCheck that the IP address is correct and that the Factory Orchestrator Service is running on the target IP.",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
_ = await failedConnectDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void IpTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(IpTextBox.Text))
|
||||
{
|
||||
ConnectButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void IpTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == Windows.System.VirtualKey.Enter)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(IpTextBox.Text))
|
||||
{
|
||||
ConnectButton_Click(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AboutButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.Frame.Navigate(typeof(AboutPage));
|
||||
}
|
||||
|
||||
private void ConfirmExit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((App)Application.Current).Exit();
|
||||
}
|
||||
|
||||
private async void ValidateXMLButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
|
||||
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.ComputerFolder;
|
||||
picker.FileTypeFilter.Add(".xml");
|
||||
Windows.Storage.StorageFolder localFolder = ApplicationData.Current.LocalFolder;
|
||||
StorageFile tempXml = await localFolder.CreateFileAsync("temp.xml", CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
Windows.Storage.StorageFile file = await picker.PickSingleFileAsync();
|
||||
if (file != null)
|
||||
{
|
||||
var path = file.Path;
|
||||
await file.CopyAndReplaceAsync(tempXml);
|
||||
|
||||
try
|
||||
{
|
||||
FactoryOrchestratorXML.Load(tempXml.Path);
|
||||
|
||||
ContentDialog successLoadDialog = new ContentDialog
|
||||
{
|
||||
Title = "FactoryOrchestratorXML successfully validated!",
|
||||
Content = $"{path} is valid FactoryOrchestratorXML.",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
_ = await successLoadDialog.ShowAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.AllExceptionsToString().Replace(tempXml.Path, path);
|
||||
ContentDialog failedLoadDialog = new ContentDialog
|
||||
{
|
||||
Title = "FactoryOrchestratorXML failed validation!",
|
||||
Content = $"{msg}",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
_ = await failedLoadDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string lastNavTag;
|
||||
private SemaphoreSlim connectionSem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.ConsolePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="5" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="5" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="CommandStack" VerticalAlignment="Top" Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="CommandConst" Text="Command:" Grid.Column="0" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" Visibility="Visible"/>
|
||||
<TextBox Name="CommandBox" AutomationProperties.LabeledBy="{Binding ElementName=CommandConst}" Text="" Grid.Column="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Margin="5" TextWrapping="Wrap" Visibility="Visible" KeyDown="CommandBox_KeyDown"/>
|
||||
<Button Name="RunButton" ToolTipService.ToolTip="Execute command" AutomationProperties.Name="Execute command" Click="RunButton_Click" Grid.Column="2">
|
||||
<SymbolIcon x:Name="RunButtonIcon" Symbol="Play" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock x:Name="OutputConst" Grid.Row="2" FontWeight="Bold" VerticalAlignment="Bottom" Padding="5,5,0,5" HorizontalAlignment="Left" Text="Console Output:"/>
|
||||
<Button x:Name="ClearButton" Click="ClearButton_Click" Grid.Row="2" Content="Clear Output" HorizontalAlignment="Right"/>
|
||||
<ScrollViewer x:Name="ScrollView" HorizontalScrollBarVisibility="Auto" Grid.Row="4">
|
||||
<StackPanel x:Name="OutputStack" Margin="5" VerticalAlignment="Top" Orientation="Vertical" SizeChanged="OutputStack_SizeChanged"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Page>
|
|
@ -0,0 +1,325 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple semi-interactive console.
|
||||
/// </summary>
|
||||
public sealed partial class ConsolePage : Page
|
||||
{
|
||||
public ConsolePage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
_cmdSem = new SemaphoreSlim(1, 1);
|
||||
_outSem = new SemaphoreSlim(1, 1);
|
||||
_newCmd = false;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
Client = ((App)Application.Current).Client;
|
||||
if ((_taskRunPoller != null) && (!_activeCmdTaskRun.TaskRunComplete))
|
||||
{
|
||||
// Only restart polling if the command is still running.
|
||||
_taskRunPoller.StartPolling(Client);
|
||||
}
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
if (_taskRunPoller != null)
|
||||
{
|
||||
_taskRunPoller.StopPolling();
|
||||
}
|
||||
base.OnNavigatedFrom(e);
|
||||
}
|
||||
|
||||
private async void RunButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_cmdSem.Wait();
|
||||
|
||||
try
|
||||
{
|
||||
if (RunButtonIcon.Symbol == Symbol.Stop)
|
||||
{
|
||||
_taskRunPoller.StopPolling();
|
||||
_taskRunPoller = null;
|
||||
await Client.AbortTaskRun(_activeCmdTaskRun.Guid);
|
||||
CommandBox.IsEnabled = true;
|
||||
RunButtonIcon.Symbol = Symbol.Play;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(CommandBox.Text))
|
||||
{
|
||||
// Asynchronously run the command
|
||||
await ExecuteCommand(CommandBox.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cmdSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async void CommandBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
// Send command if enter is pressed in command box
|
||||
if (e.Key == Windows.System.VirtualKey.Enter)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(CommandBox.Text))
|
||||
{
|
||||
// Asynchronously run the command
|
||||
await ExecuteCommand(CommandBox.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a command using cmd.exe
|
||||
/// </summary>
|
||||
private async Task ExecuteCommand(string command)
|
||||
{
|
||||
// Update UI
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
|
||||
RunButtonIcon.Symbol = Symbol.Stop;
|
||||
CommandBox.IsEnabled = false;
|
||||
|
||||
// Log command to console output
|
||||
var textBlock = new TextBlock()
|
||||
{
|
||||
Text = $"{Environment.NewLine}>{command}{Environment.NewLine}",
|
||||
FontWeight = Windows.UI.Text.FontWeights.Bold,
|
||||
IsTextSelectionEnabled = true
|
||||
};
|
||||
OutputStack.Children.Add(textBlock);
|
||||
});
|
||||
|
||||
// Execute command
|
||||
_newCmd = true;
|
||||
if (_taskRunPoller != null)
|
||||
{
|
||||
_taskRunPoller.StopPolling();
|
||||
}
|
||||
_activeCmdTaskRun = await Client.RunExecutable(@"cmd.exe", $"/C \"{command}\"", null);
|
||||
|
||||
// Watch for new output
|
||||
_taskRunPoller = new ServerPoller((Guid)_activeCmdTaskRun.Guid, typeof(TaskRun), 1000);
|
||||
_taskRunPoller.OnUpdatedObject += OnUpdatedCmdStatusAsync;
|
||||
_taskRunPoller.OnException += ((App)Application.Current).OnServerPollerException;
|
||||
_taskRunPoller.StartPolling(Client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the commands is finished, updates output
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void OnUpdatedCmdStatusAsync(object source, ServerPollerEventArgs e)
|
||||
{
|
||||
_activeCmdTaskRun = (TaskRun)e.Result;
|
||||
|
||||
if (_activeCmdTaskRun != null)
|
||||
{
|
||||
if (_activeCmdTaskRun.TaskRunComplete)
|
||||
{
|
||||
// The command finished, no need to poll more
|
||||
_taskRunPoller.StopPolling();
|
||||
}
|
||||
|
||||
while (_lastOutput != _activeCmdTaskRun.TaskOutput.Count)
|
||||
{
|
||||
var blocks = PrepareOutput();
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
_outSem.Wait();
|
||||
try
|
||||
{
|
||||
UpdateOutput(blocks);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_outSem.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (_activeCmdTaskRun.TaskRunComplete)
|
||||
{
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
// Allow new commands to run
|
||||
CommandBox.IsEnabled = true;
|
||||
RunButtonIcon.Symbol = Symbol.Play;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ClearButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_outSem.Wait();
|
||||
try
|
||||
{
|
||||
OutputStack.Children.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_outSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates UI with latest console output
|
||||
/// </summary>
|
||||
private List<(string text, bool isError)> PrepareOutput()
|
||||
{
|
||||
List <(string text, bool isError)> ret = new List<(string text, bool isError)>();
|
||||
|
||||
if (_newCmd)
|
||||
{
|
||||
_lastOutput = 0;
|
||||
_newCmd = false;
|
||||
}
|
||||
|
||||
|
||||
var endCount = Math.Min(_activeCmdTaskRun.TaskOutput.Count, _lastOutput + _maxLinesPerBlock);
|
||||
string text = "";
|
||||
bool errorBlock = false;
|
||||
|
||||
for (int i = _lastOutput; i < endCount; i++)
|
||||
{
|
||||
if (_activeCmdTaskRun.TaskOutput[i] != null)
|
||||
{
|
||||
if (errorBlock && _activeCmdTaskRun.TaskOutput[i].StartsWith("ERROR: "))
|
||||
{
|
||||
// Append error text
|
||||
text += _activeCmdTaskRun.TaskOutput[i];
|
||||
errorBlock = true;
|
||||
}
|
||||
else if (errorBlock)
|
||||
{
|
||||
// Done with error text, write out the error text and start again
|
||||
var tupl = (text, true);
|
||||
ret.Add(tupl);
|
||||
|
||||
text = _activeCmdTaskRun.TaskOutput[i];
|
||||
errorBlock = false;
|
||||
}
|
||||
else if (!errorBlock && _activeCmdTaskRun.TaskOutput[i].StartsWith("ERROR: "))
|
||||
{
|
||||
// Done with normal text, write out the normal text and start again
|
||||
var tupl = (text, false);
|
||||
ret.Add(tupl);
|
||||
|
||||
text = _activeCmdTaskRun.TaskOutput[i];
|
||||
errorBlock = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append normal text
|
||||
text += _activeCmdTaskRun.TaskOutput[i];
|
||||
errorBlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (i != (endCount - 1))
|
||||
{
|
||||
text += System.Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
_lastOutput = endCount;
|
||||
|
||||
if (!String.IsNullOrEmpty(text))
|
||||
{
|
||||
if (errorBlock)
|
||||
{
|
||||
var tupl = (text, true);
|
||||
ret.Add(tupl);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tupl = (text, false);
|
||||
ret.Add(tupl);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates UI with latest console output
|
||||
/// </summary>
|
||||
private void UpdateOutput(List<(string text, bool isError)> blocks)
|
||||
{
|
||||
foreach (var block in blocks)
|
||||
{
|
||||
var textBlock = new TextBlock()
|
||||
{
|
||||
Text = block.text,
|
||||
IsTextSelectionEnabled = true
|
||||
};
|
||||
|
||||
if (block.isError)
|
||||
{
|
||||
textBlock.FontWeight = Windows.UI.Text.FontWeights.Bold;
|
||||
textBlock.Foreground = new SolidColorBrush(Windows.UI.Colors.Red);
|
||||
}
|
||||
|
||||
if (OutputStack.Children.Count >= _maxBlocks)
|
||||
{
|
||||
OutputStack.Children.RemoveAt(0);
|
||||
}
|
||||
OutputStack.Children.Add(textBlock);
|
||||
}
|
||||
}
|
||||
|
||||
private void OutputStack_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
// Scroll down when new output is received
|
||||
StackPanel stack = (StackPanel)sender;
|
||||
ScrollViewer scrollView = (ScrollViewer)stack.Parent;
|
||||
scrollView.ChangeView(null, scrollView.ScrollableHeight, null, true);
|
||||
}
|
||||
|
||||
private TaskRun _activeCmdTaskRun;
|
||||
private bool _newCmd;
|
||||
private int _lastOutput;
|
||||
private ServerPoller _taskRunPoller;
|
||||
private SemaphoreSlim _cmdSem;
|
||||
private SemaphoreSlim _outSem;
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
|
||||
private const int _maxBlocks = 10; // @500 lines per block this is 5000 lines or 10 commands maximum
|
||||
private const int _maxLinesPerBlock = 500;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap mp rescap">
|
||||
<Identity Name="Microsoft.FactoryOrchestratorApp.DEV" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="2.0.0.0" />
|
||||
<mp:PhoneIdentity PhoneProductId="598c7e0f-acaf-4866-90a5-de8ad458aa53" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
||||
<Properties>
|
||||
<DisplayName>Factory Orchestrator (DEV)</DisplayName>
|
||||
<PublisherDisplayName>Microsoft</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
</Resources>
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="FactoryOrchestratorApp.App">
|
||||
<uap:VisualElements DisplayName="Factory Orchestrator (DEV)" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Factory Orchestrator UWP (DEV)" BackgroundColor="#000000">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square310x310Logo="Assets\LargeTile.png" Square71x71Logo="Assets\SmallTile.png">
|
||||
</uap:DefaultTile>
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#000000" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<uap:Extension Category="windows.protocol" >
|
||||
<uap:Protocol Name="fo" DesiredView="default">
|
||||
<uap:DisplayName>fo</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<Capability Name="internetClientServer" />
|
||||
<Capability Name="privateNetworkClientServer" />
|
||||
<rescap:Capability Name="packageQuery"/>
|
||||
<rescap:Capability Name="extendedExecutionUnconstrained"/>
|
||||
<rescap:Capability Name="broadFileSystemAccess"/>
|
||||
<rescap:Capability Name="confirmAppClose"/>
|
||||
</Capabilities>
|
||||
</Package>
|
|
@ -0,0 +1,192 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.EditPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Page.Resources>
|
||||
<Flyout x:Name="EditFlyout" x:Key="EditFlyout" LightDismissOverlayMode="On">
|
||||
<Grid x:Name="FlyoutGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" x:Name="EditFlyoutTextHeader" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}" Padding="5" HorizontalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Task Name:" Padding="5"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" x:Name="TestNameBox" Text="" MinWidth="400"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" x:Name="PathBlock" Text="Task Path:" Padding="5"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" x:Name="TaskPathBox" Text="" MinWidth="400"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" x:Name="AppBlock" Text="App:" Visibility="Collapsed" Padding="5"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" x:Name="AppComboBox" Visibility="Collapsed" MinWidth="400" IsEditable="True"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" x:Name="ArgumentsBlock" Text="Arguments:" Padding="5"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" x:Name="ArgumentsBox" Text="" MinWidth="400"/>
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" x:Name="TimeoutBlock" Text="Timeout (seconds):" Padding="5"/>
|
||||
<TextBox Grid.Row="4" Grid.Column="1" x:Name="TimeoutBox" Text="" InputScope="Digits" MinWidth="200" TextChanged="TimeoutBox_TextChanged"/>
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" x:Name="RetryBlock" Text="Maximum number of retries:" Padding="5"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" x:Name="RetryBox" Text="" InputScope="Digits" MinWidth="400" TextChanged="RetryBox_TextChanged"/>
|
||||
<CheckBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" x:Name="AutoPassCheck" Content="Auto Pass App on Launch?" IsChecked="False" Visibility="Collapsed" IsThreeState="False" Padding="5" Click="AutoPassCheck_Click"/>
|
||||
<CheckBox Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" x:Name="TerminateOnCompleteCheck" Content="Terminate App on Completion?" IsChecked="True" Visibility="Collapsed" IsThreeState="False" Padding="5"/>
|
||||
<CheckBox Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="2" x:Name="AbortOnFailBox" Content="Abort TaskList on Task failure?" IsChecked="False" IsThreeState="False" Padding="5"/>
|
||||
<CheckBox Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" x:Name="BgTaskBox" Content="Add as Background Task?" IsChecked="False" IsThreeState="False" Padding="5" Click="BgTaskBox_Click"/>
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="10" Grid.ColumnSpan="2" >
|
||||
<Button x:Name="CancelEdit" Padding="5" Margin="5" Click="CancelEdit_Click">
|
||||
<SymbolIcon Symbol="Cancel"/>
|
||||
</Button>
|
||||
<Button x:Name="ConfirmEdit" Padding="5" Margin="5" Click="ConfirmEdit_Click">
|
||||
<SymbolIcon Symbol="Save"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" HorizontalAlignment="Stretch" Margin="50,50,50,50" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button x:Name="BackButton" Grid.Row="0" Grid.Column="0" Click="Back_Click" Style="{StaticResource NavigationBackButtonNormalStyle}" AutomationProperties.Name="Go Back"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="1" Spacing="10">
|
||||
<TextBlock x:Name="TaskListHeader" Style="{StaticResource SubheaderTextBlockStyle}" VerticalAlignment="Center"/>
|
||||
<Button x:Name="EditListNameButton" ToolTipService.ToolTip="Rename TaskList" VerticalAlignment="Center">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="EditListNameFlyout" Opening="EditListNameFlyout_Opening">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock TextWrapping="Wrap" Text="Rename Tasklist" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}"/>
|
||||
<TextBox TextWrapping="Wrap" x:Name="RenameBox"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="CancelNameEdit" Padding="5" Margin="5" Click="CancelNameEdit_Click">
|
||||
<SymbolIcon Symbol="Cancel"/>
|
||||
</Button>
|
||||
<Button x:Name="ConfirmNameEdit" Padding="5" Margin="5" Click="ConfirmNameEdit_Click">
|
||||
<SymbolIcon Symbol="Save"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<TextBlock x:Name="TaskListHeader2" Grid.ColumnSpan="2" Style="{StaticResource SubtitleTextBlockStyle}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Spacing="10" Padding="10">
|
||||
<TextBlock x:Name="TaskListOpt" Style="{StaticResource SubtitleTextBlockStyle}" Text="TaskList Options:"/>
|
||||
<CheckBox x:Name="ParallelCheck" Content="Run Tasks in Parallel?" IsChecked="False" Click="ListCheck_Checked" IsThreeState="False"/>
|
||||
<CheckBox x:Name="BlockingCheck" Content="Allow Other TaskLists to Run?" IsChecked="False" Click="ListCheck_Checked" IsThreeState="False"/>
|
||||
<CheckBox x:Name="TerminateBgTasksCheck" Content="Terminate Background Tasks on Completion?" IsChecked="False" Click="ListCheck_Checked" IsThreeState="False"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" Padding="10,10,0,0">
|
||||
<Button x:Name="NewExecutableButton" Content="New Executable Task" Click="NewExecutableButton_Click"/>
|
||||
<Button x:Name="NewPSButton" Content="New PowerShell Task" Click="NewPSButton_Click"/>
|
||||
<Button x:Name="NewCMDButton" Content="New Batch File Task" Click="NewCMDButton_Click"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" Padding="10,10,0,0">
|
||||
<Button x:Name="NewTAEFButton" Content="New TAEF Task" Click="NewTAEFButton_Click"/>
|
||||
<Button x:Name="NewUWPButton" Content="New UWP App Task" Click="NewUWPButton_Click"/>
|
||||
<Button x:Name="NewExternalButton" Content="New External Task" Click="NewExternalButton_Click"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock x:Name="BgTasksHeader" Grid.Row="3" Grid.Column="1" Style="{StaticResource TitleTextBlockStyle}" HorizontalAlignment="Left" Text="Background Tasks:" Visibility="Collapsed"/>
|
||||
<ListView x:Name="BgTaskListView" Grid.Row="4" Grid.Column="1" SelectionMode="None" AllowDrop="True" IsItemClickEnabled="False" CanDragItems="True" CanReorderItems="True" IsSwipeEnabled="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ScrollViewer.VerticalScrollMode="Enabled" DragItemsCompleted="TaskListView_DragCompleted">
|
||||
<ListView.Resources>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListViewItem">
|
||||
<Grid x:Name="BgItemGrid" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="250" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Grid.Column="0"/>
|
||||
<ContentPresenter Grid.Column="1" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="buttonGroupBg">
|
||||
<Button x:Name="EditBackgroundListButton" Margin="10,0,5,0" Click="BgEditButton_Click">
|
||||
<SymbolIcon Symbol="Edit"/>
|
||||
</Button>
|
||||
<Button x:Name="DeleteBackgroundListButton" Margin="5,0,5,0" Click="BgDeleteButton_Click">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListView.Resources>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock x:Name="TaskName" Text="{Binding Mode=TwoWay}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<TextBlock x:Name="TasksHeader" Grid.Row="5" Grid.Column="1" Style="{StaticResource TitleTextBlockStyle}" HorizontalAlignment="Left" Text="Tasks:" Visibility="Collapsed"/>
|
||||
<ListView x:Name="TaskListView" Grid.Row="6" Grid.Column="1" SelectionMode="None" AllowDrop="True" IsItemClickEnabled="False" CanDragItems="True" CanReorderItems="True" IsSwipeEnabled="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ScrollViewer.VerticalScrollMode="Enabled" DragItemsCompleted="TaskListView_DragCompleted">
|
||||
<ListView.Resources>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListViewItem">
|
||||
<Grid x:Name="ItemGrid" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="250" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Grid.Column="0"/>
|
||||
<ContentPresenter Grid.Column="1" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="buttonGroup">
|
||||
<Button x:Name="EditListButton" Margin="10,0,5,0" Click="EditButton_Click">
|
||||
<SymbolIcon Symbol="Edit"/>
|
||||
</Button>
|
||||
<Button x:Name="DeleteListButton" Margin="5,0,5,0" Click="DeleteButton_Click">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListView.Resources>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock x:Name="TaskName" Text="{Binding Mode=TwoWay}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,737 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class EditPage : Page
|
||||
{
|
||||
public EditPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected async override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
BackButton.IsEnabled = this.Frame.CanGoBack;
|
||||
|
||||
if (e.Parameter != null)
|
||||
{
|
||||
isNewList = false;
|
||||
activeList = (TaskList)e.Parameter;
|
||||
}
|
||||
else
|
||||
{
|
||||
isNewList = true;
|
||||
activeList = new TaskList("New TaskList", Guid.NewGuid());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AppComboBox.ItemsSource = await Client.GetInstalledApps();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// WDP might not be running, just dont put any apps in the list
|
||||
AppComboBox.ItemsSource = new List<string>();
|
||||
}
|
||||
|
||||
ParallelCheck.IsChecked = activeList.RunInParallel;
|
||||
BlockingCheck.IsChecked = activeList.AllowOtherTaskListsToRun;
|
||||
TerminateBgTasksCheck.IsChecked = activeList.TerminateBackgroundTasksOnCompletion;
|
||||
UpdateHeader();
|
||||
|
||||
TasksCollection = new ObservableCollection<TaskBase>(activeList.Tasks);
|
||||
TaskListView.ItemsSource = TasksCollection;
|
||||
|
||||
BackgroundTasksCollection = new ObservableCollection<TaskBase>(activeList.BackgroundTasks);
|
||||
BgTaskListView.ItemsSource = BackgroundTasksCollection;
|
||||
|
||||
if (BackgroundTasksCollection.Count > 0)
|
||||
{
|
||||
BgTasksHeader.Visibility = Visibility.Visible;
|
||||
}
|
||||
if (TasksCollection.Count > 0)
|
||||
{
|
||||
TasksHeader.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
listEdited = false;
|
||||
|
||||
|
||||
var style = new Style(typeof(FlyoutPresenter));
|
||||
style.Setters.Add(new Setter(FlyoutPresenter.MinWidthProperty, Window.Current.CoreWindow.Bounds.Width));
|
||||
style.Setters.Add(new Setter(FlyoutPresenter.MinHeightProperty, Window.Current.CoreWindow.Bounds.Height));
|
||||
EditFlyout.SetValue(Flyout.FlyoutPresenterStyleProperty, style);
|
||||
}
|
||||
|
||||
private void UpdateHeader()
|
||||
{
|
||||
TaskListHeader.Text = $"Editing TaskList: {activeList.Name}";
|
||||
TaskListHeader2.Text = $"({activeList.Guid.ToString()})";
|
||||
}
|
||||
|
||||
private async void Back_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await On_BackRequested();
|
||||
}
|
||||
|
||||
private async Task<bool> On_BackRequested()
|
||||
{
|
||||
if (this.Frame.CanGoBack)
|
||||
{
|
||||
if (listEdited)
|
||||
{
|
||||
ContentDialog deleteFileDialog = new ContentDialog
|
||||
{
|
||||
Title = "Save TaskList?",
|
||||
Content = "Do you want to save your changes?",
|
||||
PrimaryButtonText = "Yes",
|
||||
SecondaryButtonText = "No",
|
||||
CloseButtonText = "Cancel"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await deleteFileDialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
if (await SaveTaskList())
|
||||
{
|
||||
this.Frame.GoBack();
|
||||
}
|
||||
}
|
||||
else if (result == ContentDialogResult.Secondary)
|
||||
{
|
||||
this.Frame.GoBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Frame.GoBack();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void BgDeleteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BackgroundTasksCollection.Remove(GetTestFromButton(sender as Button));
|
||||
listEdited = true;
|
||||
|
||||
if (BackgroundTasksCollection.Count == 0)
|
||||
{
|
||||
BgTasksHeader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void BgEditButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = GetTestFromButton(sender as Button);
|
||||
activeTaskIndex = BackgroundTasksCollection.IndexOf(activeTask);
|
||||
ConfigureFlyout(activeTask.Type, true);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions()
|
||||
{
|
||||
Placement = FlyoutPlacementMode.Auto,
|
||||
ShowMode = FlyoutShowMode.Standard
|
||||
});
|
||||
}
|
||||
|
||||
private void DeleteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TasksCollection.Remove(GetTestFromButton(sender as Button));
|
||||
listEdited = true;
|
||||
|
||||
if (TasksCollection.Count == 0)
|
||||
{
|
||||
TasksHeader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void EditButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = GetTestFromButton(sender as Button);
|
||||
activeTaskIndex = TasksCollection.IndexOf(activeTask);
|
||||
ConfigureFlyout(activeTask.Type);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions()
|
||||
{
|
||||
Placement = FlyoutPlacementMode.Auto,
|
||||
ShowMode = FlyoutShowMode.Standard
|
||||
});
|
||||
}
|
||||
|
||||
private TaskBase CreateTestFromFlyout(TaskType testType)
|
||||
{
|
||||
activeTaskIsNowBg = false;
|
||||
if (activeTask == null)
|
||||
{
|
||||
activeTaskIndex = -1;
|
||||
switch (testType)
|
||||
{
|
||||
case TaskType.ConsoleExe:
|
||||
activeTask = new ExecutableTask(TaskPathBox.Text);
|
||||
break;
|
||||
case TaskType.UWP:
|
||||
activeTask = new UWPTask(AppComboBox.SelectedItem.ToString());
|
||||
break;
|
||||
case TaskType.External:
|
||||
activeTask = new ExternalTask(TaskPathBox.Text);
|
||||
break;
|
||||
case TaskType.TAEFDll:
|
||||
activeTask = new TAEFTest(TaskPathBox.Text);
|
||||
break;
|
||||
case TaskType.BatchFile:
|
||||
activeTask = new BatchFileTask(TaskPathBox.Text);
|
||||
break;
|
||||
case TaskType.PowerShell:
|
||||
activeTask = new PowerShellTask(TaskPathBox.Text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(TestNameBox.Text))
|
||||
{
|
||||
activeTask.Name = TestNameBox.Text;
|
||||
}
|
||||
|
||||
if (TimeoutBox.Text != "")
|
||||
{
|
||||
try
|
||||
{
|
||||
activeTask.TimeoutSeconds = Int32.Parse(TimeoutBox.Text);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
activeTask.TimeoutSeconds = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTask.TimeoutSeconds = -1;
|
||||
}
|
||||
|
||||
if (RetryBox.Text != "")
|
||||
{
|
||||
try
|
||||
{
|
||||
activeTask.MaxNumberOfRetries = UInt32.Parse(RetryBox.Text);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
activeTask.MaxNumberOfRetries = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTask.MaxNumberOfRetries = 0;
|
||||
}
|
||||
|
||||
switch (testType)
|
||||
{
|
||||
case TaskType.ConsoleExe:
|
||||
case TaskType.BatchFile:
|
||||
case TaskType.PowerShell:
|
||||
case TaskType.TAEFDll:
|
||||
case TaskType.External:
|
||||
var task = activeTask as TaskBase;
|
||||
task.Path = TaskPathBox.Text;
|
||||
task.Arguments = ArgumentsBox.Text;
|
||||
break;
|
||||
case TaskType.UWP:
|
||||
var uwpTask = activeTask as UWPTask;
|
||||
uwpTask.Path = AppComboBox.SelectedItem.ToString();
|
||||
uwpTask.Arguments = ArgumentsBox.Text;
|
||||
uwpTask.AutoPassedIfLaunched = (bool)AutoPassCheck.IsChecked;
|
||||
uwpTask.TerminateOnCompleted = (bool)TerminateOnCompleteCheck.IsChecked;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (testType)
|
||||
{
|
||||
case TaskType.ConsoleExe:
|
||||
case TaskType.BatchFile:
|
||||
case TaskType.PowerShell:
|
||||
var task = activeTask as ExecutableTask;
|
||||
task.BackgroundTask = (bool)BgTaskBox.IsChecked;
|
||||
if (task.BackgroundTask)
|
||||
{
|
||||
activeTaskIsNowBg = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
activeTask.AbortTaskListOnFailed = (bool)AbortOnFailBox.IsChecked;
|
||||
|
||||
return activeTask;
|
||||
}
|
||||
|
||||
private void TimeoutBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Int32.Parse(TimeoutBox.Text);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
TimeoutBox.Text = "";
|
||||
}
|
||||
}
|
||||
|
||||
private void RetryBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
UInt32.Parse(RetryBox.Text);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
RetryBox.Text = "";
|
||||
}
|
||||
}
|
||||
|
||||
private void BgTaskBox_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((bool)BgTaskBox.IsChecked)
|
||||
{
|
||||
TimeoutBox.IsEnabled = false;
|
||||
RetryBox.IsEnabled = false;
|
||||
AbortOnFailBox.IsEnabled = false;
|
||||
TimeoutBox.Text = "-1";
|
||||
RetryBox.Text = "0";
|
||||
}
|
||||
else
|
||||
{
|
||||
TimeoutBox.IsEnabled = true;
|
||||
RetryBox.IsEnabled = true;
|
||||
AbortOnFailBox.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoPassCheck_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((bool)AutoPassCheck.IsChecked)
|
||||
{
|
||||
TerminateOnCompleteCheck.IsEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
TerminateOnCompleteCheck.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConfigureFlyout(TaskType testType, bool editingBgTask = false)
|
||||
{
|
||||
activeTaskType = testType;
|
||||
activeTaskWasBg = editingBgTask;
|
||||
|
||||
if (activeTask != null)
|
||||
{
|
||||
TestNameBox.Text = activeTask.Name;
|
||||
TimeoutBox.Text = activeTask.TimeoutSeconds.ToString();
|
||||
RetryBox.Text = activeTask.MaxNumberOfRetries.ToString();
|
||||
|
||||
switch (testType)
|
||||
{
|
||||
case TaskType.ConsoleExe:
|
||||
var exeTest = activeTask as ExecutableTask;
|
||||
TaskPathBox.Text = exeTest.Path;
|
||||
ArgumentsBox.Text = exeTest.Arguments;
|
||||
EditFlyoutTextHeader.Text = $"Editing Executable Task";
|
||||
BgTaskBox.IsChecked = exeTest.BackgroundTask;
|
||||
break;
|
||||
case TaskType.UWP:
|
||||
var uwpTask = activeTask as UWPTask;
|
||||
AppComboBox.SelectedItem = uwpTask.Path;
|
||||
|
||||
if (AppComboBox.SelectedIndex == -1)
|
||||
{
|
||||
List<string> itemlist;
|
||||
try
|
||||
{
|
||||
itemlist = await Client.GetInstalledApps();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// WDP might not be running, just dont put any apps in the list
|
||||
itemlist = new List<string>();
|
||||
}
|
||||
|
||||
if (!itemlist.Contains(uwpTask.Path))
|
||||
{
|
||||
itemlist.Add(uwpTask.Path);
|
||||
}
|
||||
|
||||
AppComboBox.ItemsSource = itemlist;
|
||||
AppComboBox.SelectedItem = uwpTask.Path;
|
||||
}
|
||||
AutoPassCheck.IsChecked = uwpTask.AutoPassedIfLaunched;
|
||||
TerminateOnCompleteCheck.IsChecked = uwpTask.TerminateOnCompleted;
|
||||
// Disable Terminate box if needed
|
||||
AutoPassCheck_Click(null, null);
|
||||
|
||||
EditFlyoutTextHeader.Text = $"Editing UWP Task";
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
case TaskType.External:
|
||||
var externalTest = activeTask as ExternalTask;
|
||||
TaskPathBox.Text = externalTest.Path;
|
||||
ArgumentsBox.Text = externalTest.Arguments;
|
||||
PathBlock.Text = "Image or Video Path: ";
|
||||
EditFlyoutTextHeader.Text = $"Editing External Task";
|
||||
break;
|
||||
case TaskType.TAEFDll:
|
||||
var taefTest = activeTask as TAEFTest;
|
||||
TaskPathBox.Text = taefTest.Path;
|
||||
ArgumentsBox.Text = taefTest.Arguments;
|
||||
EditFlyoutTextHeader.Text = $"Editing TAEF Test";
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
case TaskType.PowerShell:
|
||||
var script = activeTask as PowerShellTask;
|
||||
TaskPathBox.Text = script.Path;
|
||||
ArgumentsBox.Text = script.Arguments;
|
||||
EditFlyoutTextHeader.Text = $"Editing PowerShell Task";
|
||||
BgTaskBox.IsChecked = script.BackgroundTask;
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
case TaskType.BatchFile:
|
||||
var cmd = activeTask as BatchFileTask;
|
||||
TaskPathBox.Text = cmd.Path;
|
||||
ArgumentsBox.Text = cmd.Arguments;
|
||||
EditFlyoutTextHeader.Text = $"Editing Batch File Task";
|
||||
BgTaskBox.IsChecked = cmd.BackgroundTask;
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (testType)
|
||||
{
|
||||
case TaskType.ConsoleExe:
|
||||
EditFlyoutTextHeader.Text = $"New Executable Task";
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
case TaskType.UWP:
|
||||
EditFlyoutTextHeader.Text = $"New UWP Task";
|
||||
AutoPassCheck.IsChecked = false;
|
||||
TerminateOnCompleteCheck.IsChecked = true;
|
||||
// Enable Terminate box if needed
|
||||
AutoPassCheck_Click(null, null);
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
case TaskType.External:
|
||||
EditFlyoutTextHeader.Text = $"New External Task";
|
||||
PathBlock.Text = "Image or Video Path: ";
|
||||
break;
|
||||
case TaskType.TAEFDll:
|
||||
EditFlyoutTextHeader.Text = $"New TAEF Test";
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
case TaskType.PowerShell:
|
||||
EditFlyoutTextHeader.Text = $"New PowerShell Task";
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
case TaskType.BatchFile:
|
||||
EditFlyoutTextHeader.Text = $"New Batch Task";
|
||||
PathBlock.Text = "Path: ";
|
||||
break;
|
||||
}
|
||||
|
||||
var boxes = FlyoutGrid.Children.Where(x => x.GetType() == typeof(TextBox));
|
||||
foreach (var item in boxes)
|
||||
{
|
||||
var box = item as TextBox;
|
||||
box.Text = "";
|
||||
}
|
||||
}
|
||||
|
||||
switch (testType)
|
||||
{
|
||||
case TaskType.ConsoleExe:
|
||||
case TaskType.PowerShell:
|
||||
case TaskType.BatchFile:
|
||||
PathBlock.Visibility = Visibility.Visible;
|
||||
TaskPathBox.Visibility = Visibility.Visible;
|
||||
AppComboBox.Visibility = Visibility.Collapsed;
|
||||
AppBlock.Visibility = Visibility.Collapsed;
|
||||
ArgumentsBlock.Text = "Arguments:";
|
||||
ArgumentsBlock.Visibility = Visibility.Visible;
|
||||
ArgumentsBox.Visibility = Visibility.Visible;
|
||||
BgTaskBox.Visibility = Visibility.Visible;
|
||||
AutoPassCheck.Visibility = Visibility.Collapsed;
|
||||
TerminateOnCompleteCheck.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
case TaskType.External:
|
||||
case TaskType.TAEFDll:
|
||||
PathBlock.Visibility = Visibility.Visible;
|
||||
TaskPathBox.Visibility = Visibility.Visible;
|
||||
AppComboBox.Visibility = Visibility.Collapsed;
|
||||
AppBlock.Visibility = Visibility.Collapsed;
|
||||
ArgumentsBlock.Text = "Arguments:";
|
||||
ArgumentsBlock.Visibility = Visibility.Visible;
|
||||
ArgumentsBox.Visibility = Visibility.Visible;
|
||||
BgTaskBox.Visibility = Visibility.Collapsed;
|
||||
AutoPassCheck.Visibility = Visibility.Collapsed;
|
||||
TerminateOnCompleteCheck.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
case TaskType.UWP:
|
||||
PathBlock.Visibility = Visibility.Collapsed;
|
||||
TaskPathBox.Visibility = Visibility.Collapsed;
|
||||
AppComboBox.Visibility = Visibility.Visible;
|
||||
AppBlock.Visibility = Visibility.Visible;
|
||||
ArgumentsBlock.Text = "Arguments (NOT passed to app, reference only):";
|
||||
ArgumentsBlock.Visibility = Visibility.Visible;
|
||||
ArgumentsBox.Visibility = Visibility.Visible;
|
||||
BgTaskBox.Visibility = Visibility.Collapsed;
|
||||
AutoPassCheck.Visibility = Visibility.Visible;
|
||||
TerminateOnCompleteCheck.Visibility = Visibility.Visible;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void NewExecutableButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = null;
|
||||
ConfigureFlyout(TaskType.ConsoleExe);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions() { Placement = FlyoutPlacementMode.Auto });
|
||||
}
|
||||
|
||||
private void NewTAEFButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = null;
|
||||
ConfigureFlyout(TaskType.TAEFDll);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions() { Placement = FlyoutPlacementMode.Auto });
|
||||
}
|
||||
|
||||
private void NewUWPButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = null;
|
||||
ConfigureFlyout(TaskType.UWP);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions() { Placement = FlyoutPlacementMode.Auto });
|
||||
}
|
||||
|
||||
private void NewExternalButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = null;
|
||||
ConfigureFlyout(TaskType.External);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions() { Placement = FlyoutPlacementMode.Auto });
|
||||
}
|
||||
private void NewPSButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = null;
|
||||
ConfigureFlyout(TaskType.PowerShell);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions() { Placement = FlyoutPlacementMode.Auto });
|
||||
}
|
||||
|
||||
private void NewCMDButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = null;
|
||||
ConfigureFlyout(TaskType.BatchFile);
|
||||
EditFlyout.ShowAt(LayoutRoot, new FlyoutShowOptions() { Placement = FlyoutPlacementMode.Auto });
|
||||
}
|
||||
|
||||
private void CancelEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = null;
|
||||
activeTaskIndex = -1;
|
||||
EditFlyout.Hide();
|
||||
}
|
||||
|
||||
private void ConfirmEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeTask = CreateTestFromFlyout(activeTaskType);
|
||||
|
||||
if (activeTask != null)
|
||||
{
|
||||
if (activeTaskIsNowBg)
|
||||
{
|
||||
if (activeTaskWasBg && activeTaskIndex >= 0)
|
||||
{
|
||||
BackgroundTasksCollection[activeTaskIndex] = activeTask;
|
||||
}
|
||||
else if (!activeTaskWasBg && activeTaskIndex >= 0)
|
||||
{
|
||||
BackgroundTasksCollection.Add(activeTask);
|
||||
TasksCollection.RemoveAt(activeTaskIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
BackgroundTasksCollection.Add(activeTask);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (activeTaskWasBg && activeTaskIndex >= 0)
|
||||
{
|
||||
TasksCollection.Add(activeTask);
|
||||
BackgroundTasksCollection.RemoveAt(activeTaskIndex);
|
||||
}
|
||||
else if (!activeTaskWasBg && activeTaskIndex >= 0)
|
||||
{
|
||||
TasksCollection[activeTaskIndex] = activeTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
TasksCollection.Add(activeTask);
|
||||
}
|
||||
}
|
||||
|
||||
listEdited = true;
|
||||
}
|
||||
|
||||
if (BackgroundTasksCollection.Count > 0)
|
||||
{
|
||||
BgTasksHeader.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
BgTasksHeader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
if (TasksCollection.Count > 0)
|
||||
{
|
||||
TasksHeader.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
TasksHeader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
EditFlyout.Hide();
|
||||
}
|
||||
|
||||
private async Task<bool> SaveTaskList()
|
||||
{
|
||||
activeList.Tasks = new List<TaskBase>();
|
||||
activeList.BackgroundTasks = new List<TaskBase>();
|
||||
foreach (var task in TasksCollection)
|
||||
{
|
||||
activeList.Tasks.Add(task);
|
||||
}
|
||||
foreach (var task in BackgroundTasksCollection)
|
||||
{
|
||||
activeList.BackgroundTasks.Add(task);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (isNewList)
|
||||
{
|
||||
await Client.CreateTaskListFromTaskList(activeList);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Client.UpdateTaskList(activeList);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.GetType() != typeof(FactoryOrchestratorConnectionException))
|
||||
{
|
||||
ContentDialog failedSaveDialog = new ContentDialog
|
||||
{
|
||||
Title = "Failed to save TaskList",
|
||||
Content = ex.Message,
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await failedSaveDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TaskListView_DragCompleted(UIElement sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
listEdited = true;
|
||||
}
|
||||
|
||||
private TaskBase GetTestFromButton(Button button)
|
||||
{
|
||||
var stack = button.Parent as StackPanel;
|
||||
var grid = stack.Parent as Grid;
|
||||
return ((ContentPresenter)(grid.Children.Where(x => x.GetType() == typeof(ContentPresenter)).First())).Content as TaskBase;
|
||||
}
|
||||
|
||||
private void ListCheck_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
activeList.AllowOtherTaskListsToRun = (bool)BlockingCheck.IsChecked;
|
||||
activeList.RunInParallel = (bool)ParallelCheck.IsChecked;
|
||||
activeList.TerminateBackgroundTasksOnCompletion = (bool)TerminateBgTasksCheck.IsChecked;
|
||||
listEdited = true;
|
||||
}
|
||||
|
||||
private void CancelNameEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EditListNameFlyout.Hide();
|
||||
}
|
||||
|
||||
private async void ConfirmNameEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RenameBox.Text))
|
||||
{
|
||||
ContentDialog failedEdit = new ContentDialog
|
||||
{
|
||||
Title = "Name must not be empty!",
|
||||
Content = "The TaskList name must not be empty!",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
RenameBox.Text = activeList.Name;
|
||||
|
||||
ContentDialogResult result = await failedEdit.ShowAsync();
|
||||
}
|
||||
else if (!activeList.Name.Equals(RenameBox.Text, StringComparison.InvariantCulture))
|
||||
{
|
||||
activeList.Name = RenameBox.Text;
|
||||
listEdited = true;
|
||||
UpdateHeader();
|
||||
EditListNameFlyout.Hide();
|
||||
}
|
||||
}
|
||||
private void EditListNameFlyout_Opening(object sender, object e)
|
||||
{
|
||||
RenameBox.Text = activeList.Name;
|
||||
}
|
||||
|
||||
private ObservableCollection<TaskBase> TasksCollection;
|
||||
private ObservableCollection<TaskBase> BackgroundTasksCollection;
|
||||
private TaskList activeList;
|
||||
private TaskBase activeTask;
|
||||
private TaskType activeTaskType;
|
||||
private int activeTaskIndex;
|
||||
private bool activeTaskWasBg;
|
||||
private bool activeTaskIsNowBg;
|
||||
private bool isNewList;
|
||||
private bool listEdited;
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.ExternalTestResultPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid Width="Auto" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="12*"/>
|
||||
<RowDefinition Height="50*"/>
|
||||
<RowDefinition Height="15*"/>
|
||||
<RowDefinition Height="35*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Padding="15">
|
||||
<TextBlock Text="Action Required" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="35" FontWeight="Bold"/>
|
||||
<TextBlock x:Name ="TestText" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="15"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Padding="20">
|
||||
<TextBlock x:Name="MediaProblems" FontSize="20" Visibility="Collapsed" TextWrapping="Wrap"></TextBlock>
|
||||
<Image x:Name ="InstructionalImage" Visibility="Collapsed" Width="500"/>
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<RelativePanel>
|
||||
<MediaPlayerElement x:Name="InstructionalVideo" AutoPlay="True" Width="500">
|
||||
</MediaPlayerElement>
|
||||
<StackPanel Orientation="Horizontal" RelativePanel.Below="InstructionalVideo" x:Name="VideoButtonTray" Visibility="Collapsed">
|
||||
<Button x:Name="InstructionalVideoPause" Click="InstructionalVideoPause_Click" ToolTipService.ToolTip="Pause" AutomationProperties.Name="Pause">
|
||||
<Button.Content>
|
||||
<SymbolIcon Symbol="Pause"/>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<Button x:Name="InstructionalVideoPlay" Click="InstructionalVideoPlay_Click" ToolTipService.ToolTip="Play" AutomationProperties.Name="Play">
|
||||
<Button.Content>
|
||||
<SymbolIcon Symbol="Play"/>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
</RelativePanel>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Left" Padding="20" VerticalAlignment="Center" Grid.Row="2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="PathTextLabel" Text="Path" HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="20" Visibility="Collapsed" Grid.Row="1" Grid.Column="0" FontWeight="Bold"/>
|
||||
<TextBlock x:Name="PathText" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="20" Visibility="Collapsed" Grid.Row="1" Grid.Column="1" Margin="10,0,0,0"/>
|
||||
|
||||
<TextBlock x:Name="ArgsTextLabel" Text="Arguments" HorizontalAlignment="Right" FontSize="20" Visibility="Collapsed" Grid.Row="2" Grid.Column="0" FontWeight="Bold"/>
|
||||
<TextBlock x:Name="ArgsText" HorizontalAlignment="Left" FontSize="20" Grid.Row="2" Visibility="Collapsed" Grid.Column="1" Margin="10,0,0,0"/>
|
||||
|
||||
<TextBlock x:Name="TaskRunTextLabel" Text="Task Run" HorizontalAlignment="Right" Visibility="Collapsed" FontSize="20" Grid.Row="3" Grid.Column="0" FontWeight="Bold"/>
|
||||
<TextBlock x:Name="TaskRunText" HorizontalAlignment="Left" Visibility="Visible" FontSize="20" Grid.Row="3" Grid.Column="1" Margin="10,0,0,0"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="3" Padding ="15" Background="{ThemeResource SystemBaseLowColor}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="CommentBlock" Text="Notes (Optional)" Grid.Column="1" Padding="10" FontSize="25"/>
|
||||
<TextBox x:Name="CommentBox" Grid.Column="1" TextWrapping="Wrap" AcceptsReturn="True" MinHeight="100" AutomationProperties.LabeledBy="{Binding ElementName=CommentBlock}"/>
|
||||
</Grid>
|
||||
<Grid HorizontalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button x:Name="PassButton" Grid.Column="0" Margin="20" FontSize="50" Click="PassButton_Click" Content="Pass ✔️"/>
|
||||
<Button x:Name="FailButton" Grid.Column="1" Margin="20" FontSize="50" Content="Fail ❌" Click="FailButton_Click"/>
|
||||
<Button x:Name="AbortButton" Grid.Column="2" Margin="20" FontSize="50" Content="Abort ⛔" Click="AbortButton_Click"/>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,346 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Core;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// A result entry page that queries the user if a given TaskRun for an external/UWP task passed or failed on the DUT.
|
||||
/// The page is automatically navigated from if the TaskRun is "completed" by a remote FO client.
|
||||
/// </summary>
|
||||
public sealed partial class ExternalTestResultPage : Page
|
||||
{
|
||||
public ExternalTestResultPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
updateLock = new object();
|
||||
testReportReady = false;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
Client = ((App)Application.Current).Client;
|
||||
|
||||
// Get TaskRun we are reporting results for
|
||||
taskRun = ((App)Application.Current).RunWaitingForResult;
|
||||
|
||||
taskRunPoller = new ServerPoller(taskRun.Guid, typeof(TaskRun), 1000);
|
||||
taskRunPoller.OnUpdatedObject += OnUpdatedRun;
|
||||
taskRunPoller.OnException += TaskRunPoller_OnException;
|
||||
taskRunPoller.StartPolling(Client);
|
||||
|
||||
// Append task details to UI
|
||||
string taskRunText = taskRun.TaskName;
|
||||
if (!String.IsNullOrEmpty(taskRunText))
|
||||
{
|
||||
TestText.Text = taskRunText;
|
||||
TestText.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
if (taskRun.TaskPath != taskRun.TaskName)
|
||||
{
|
||||
string taskRunPath = taskRun.TaskPath;
|
||||
if (!String.IsNullOrEmpty(taskRunPath))
|
||||
{
|
||||
PathText.Text = taskRunPath;
|
||||
PathText.Visibility = Visibility.Visible;
|
||||
PathTextLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
string mediaPath = taskRun.TaskPath;
|
||||
MediaType mediaType = GetInstructionalMediaType(mediaPath);
|
||||
if (mediaType == MediaType.Image)
|
||||
{
|
||||
AddSourceToImage(mediaPath, InstructionalImage, MediaProblems);
|
||||
}
|
||||
else if (mediaType == MediaType.Video)
|
||||
{
|
||||
AddSourceToVideoAndDisplay(mediaPath, InstructionalVideo, MediaProblems);
|
||||
}
|
||||
}
|
||||
|
||||
string argsString = taskRun.Arguments;
|
||||
string taskRunString = taskRun.Guid.ToString();
|
||||
if (!String.IsNullOrEmpty(argsString))
|
||||
{
|
||||
ArgsText.Text = argsString;
|
||||
ArgsText.Visibility = Visibility.Visible;
|
||||
ArgsTextLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(taskRunString))
|
||||
{
|
||||
TaskRunText.Text = taskRunString;
|
||||
TaskRunText.Visibility = Visibility.Visible;
|
||||
TaskRunTextLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the file path to the media, it will determine if the file extenstion is one of the supported image file extensions or video file extensions, and return string "image" or "video", respectively
|
||||
/// </summary>
|
||||
/// <param name="mediaPath"></param>
|
||||
/// <returns></returns>
|
||||
private MediaType GetInstructionalMediaType(string mediaPath)
|
||||
{
|
||||
// TODO: Import all of the supported extensions from config.json, and store it in a scope outside of this function and page so it doesn't parse a json tree every time this is run
|
||||
|
||||
// Strip the file path to be just the file extension
|
||||
string mediaType = Path.GetExtension(mediaPath);
|
||||
|
||||
// Check if the path ends in a supported image extension
|
||||
if (SupportedImageExtensions.Any(x => x.Equals(mediaType, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return MediaType.Image;
|
||||
}
|
||||
if (SupportedVideoExtensions.Any(x => x.Equals(mediaType, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return MediaType.Video;
|
||||
}
|
||||
|
||||
return MediaType.None;
|
||||
}
|
||||
|
||||
private async void AddSourceToImage(string devicePath, Image img, TextBlock errorText)
|
||||
{
|
||||
string desiredName = Path.GetFileName(devicePath);
|
||||
|
||||
try
|
||||
{
|
||||
string newPath = Path.Combine(localFolder.Path, desiredName);
|
||||
await Client.GetFileFromDevice(devicePath, newPath);
|
||||
BitmapImage bitmapImage = new BitmapImage();
|
||||
bitmapImage.UriSource = new Uri(img.BaseUri, newPath);
|
||||
img.Source = bitmapImage;
|
||||
img.Visibility = Visibility.Visible;
|
||||
}
|
||||
catch (Exception addImage)
|
||||
{
|
||||
errorText.Text = addImage.ToString();
|
||||
errorText.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddSourceToVideoAndDisplay(string devicePath, MediaPlayerElement mediaPlayer, TextBlock errorText)
|
||||
{
|
||||
if (devicePath != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create a new file in the current folder.
|
||||
string desiredName = Path.GetFileName(devicePath);
|
||||
string newPath = Path.Combine(localFolder.Path, desiredName);
|
||||
await Client.GetFileFromDevice(devicePath, newPath);
|
||||
StorageFile videoFile = await StorageFile.GetFileFromPathAsync(newPath);
|
||||
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(newPath));
|
||||
mediaPlayer.MediaPlayer.IsLoopingEnabled = true;
|
||||
mediaPlayer.Visibility = Visibility.Visible;
|
||||
VideoButtonTray.Visibility = Visibility.Visible;
|
||||
mediaPlayer.MediaPlayer.Play();
|
||||
}
|
||||
catch(Exception videoException)
|
||||
{
|
||||
errorText.Text = videoException.ToString();
|
||||
errorText.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void TaskRunPoller_OnException(object source, ServerPollerExceptionHandlerArgs e)
|
||||
{
|
||||
if (e.Exception.GetType() == typeof(FactoryOrchestratorUnkownGuidException))
|
||||
{
|
||||
// Run no longer valid, mark as aborted
|
||||
taskRun.TaskStatus = TaskStatus.Aborted;
|
||||
|
||||
_ = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
ExitPage();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Call global error handler
|
||||
((App)Application.Current).OnServerPollerException(source, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
if (taskRunPoller != null)
|
||||
{
|
||||
taskRunPoller.StopPolling();
|
||||
taskRunPoller = null;
|
||||
}
|
||||
InstructionalVideo.MediaPlayer.Dispose();
|
||||
base.OnNavigatedFrom(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Periodically checks if the TaskRun has been completed.
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnUpdatedRun(object source, ServerPollerEventArgs e)
|
||||
{
|
||||
lock (updateLock)
|
||||
{
|
||||
if (!testReportReady)
|
||||
{
|
||||
taskRun = (TaskRun)e.Result;
|
||||
|
||||
if (taskRun.TaskRunComplete)
|
||||
{
|
||||
_ = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
ExitPage();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AbortButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ReportTaskRunResultAsync(TaskStatus.Aborted);
|
||||
}
|
||||
|
||||
private void FailButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ReportTaskRunResultAsync(TaskStatus.Failed);
|
||||
}
|
||||
|
||||
private void PassButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ReportTaskRunResultAsync(TaskStatus.Passed);
|
||||
}
|
||||
|
||||
private async void ReportTaskRunResultAsync(TaskStatus result)
|
||||
{
|
||||
lock (updateLock)
|
||||
{
|
||||
// Prevent OnUpdatedRun from firing
|
||||
taskRunPoller.StopPolling();
|
||||
|
||||
if (taskRun.TaskRunComplete)
|
||||
{
|
||||
// The task was finished right before user interaction. Return, the poll event handler will exit the page.
|
||||
return;
|
||||
}
|
||||
|
||||
testReportReady = true;
|
||||
taskRun.TaskStatus = result;
|
||||
if (!String.IsNullOrWhiteSpace(CommentBox.Text))
|
||||
{
|
||||
taskRun.TaskOutput.Add("------- Start Comments -------");
|
||||
foreach (var line in CommentBox.Text.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None))
|
||||
{
|
||||
taskRun.TaskOutput.Add(line);
|
||||
}
|
||||
taskRun.TaskOutput.Add("------- End Comments -------");
|
||||
}
|
||||
|
||||
if (result != TaskStatus.Aborted)
|
||||
{
|
||||
// Don't consider the task "done" until the task passed/failed and that result was chosen by the user.
|
||||
// This is consistent with how FactoryOrchestratorServer handles exe & TAEF tests.
|
||||
taskRun.TimeFinished = DateTime.Now;
|
||||
|
||||
// Set the exit code
|
||||
taskRun.ExitCode = result == (TaskStatus.Passed) ? 0 : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Report selected result to server
|
||||
bool updated = false;
|
||||
while (!updated)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Client.UpdateTaskRun(taskRun);
|
||||
updated = true;
|
||||
}
|
||||
catch (FactoryOrchestratorUnkownGuidException)
|
||||
{
|
||||
// Run no longer valid, mark as aborted
|
||||
taskRun.TaskStatus = TaskStatus.Aborted;
|
||||
}
|
||||
catch (FactoryOrchestratorConnectionException)
|
||||
{
|
||||
((App)Application.Current).OnConnectionFailure();
|
||||
while ((((App)Application.Current).OnConnectionPage) || (!Client.IsConnected))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExitPage();
|
||||
}
|
||||
|
||||
private void ExitPage()
|
||||
{
|
||||
// Update App task, so the ServiceEvent code knows we finished
|
||||
((App)Application.Current).RunWaitingForResult.TaskStatus = taskRun.TaskStatus;
|
||||
|
||||
if (this.Frame.CanGoBack)
|
||||
{
|
||||
// Return to last page
|
||||
this.Frame.GoBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return to MainPage
|
||||
this.Frame.Navigate(typeof(MainPage));
|
||||
}
|
||||
}
|
||||
|
||||
private void InstructionalVideoPause_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InstructionalVideo.MediaPlayer.Pause();
|
||||
}
|
||||
|
||||
private void InstructionalVideoPlay_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InstructionalVideo.MediaPlayer.Play();
|
||||
}
|
||||
|
||||
private enum MediaType
|
||||
{
|
||||
Image,
|
||||
Video,
|
||||
None
|
||||
}
|
||||
private readonly StorageFolder localFolder = ApplicationData.Current.LocalFolder;
|
||||
// List of all supported image extensions
|
||||
private readonly List<string> SupportedImageExtensions = new List<string> { ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" };
|
||||
// List of all supported video extensions
|
||||
private readonly List<string> SupportedVideoExtensions = new List<string> { ".MP4", ".AVI", ".WMV" };
|
||||
private bool testReportReady;
|
||||
private TaskRun taskRun;
|
||||
private ServerPoller taskRunPoller;
|
||||
private object updateLock;
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProjectGuid>{BAD1C9B4-70E1-40F7-92AC-9B1E2049E9C5}</ProjectGuid>
|
||||
<OutputType>AppContainerExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.FactoryOrchestrator.UWP</RootNamespace>
|
||||
<AssemblyName>FactoryOrchestratorApp</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.17763.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
|
||||
<PackageCertificateKeyFile>App_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<AppInstallerUpdateFrequency>0</AppInstallerUpdateFrequency>
|
||||
<AppInstallerCheckForUpdateFrequency>OnApplicationRun</AppInstallerCheckForUpdateFrequency>
|
||||
<AppxBundle>Never</AppxBundle>
|
||||
<PackageCertificateThumbprint>51BEF46E5163B0F513BACB90EB2E8114627EC366</PackageCertificateThumbprint>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>false</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>false</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>false</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(XES_OUTDIR)' == ''">
|
||||
<OutputPath>..\bin\$(Configuration)\$(Platform)\$(AssemblyName)</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(XES_OUTDIR)' != ''">
|
||||
<OutputPath>$(BUILD_ARTIFACTSTAGINGDIRECTORY)\bin\$(Configuration)\$(Platform)\$(AssemblyName)</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AboutPage.xaml.cs">
|
||||
<DependentUpon>AboutPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="AppsPage.xaml.cs">
|
||||
<DependentUpon>AppsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ResultsViewSelector.cs" />
|
||||
<Compile Include="TaskListViewSelector.cs" />
|
||||
<Compile Include="ConsolePage.xaml.cs">
|
||||
<DependentUpon>ConsolePage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ConnectionPage.xaml.cs">
|
||||
<DependentUpon>ConnectionPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="EditPage.xaml.cs">
|
||||
<DependentUpon>EditPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ExternalTestResultPage.xaml.cs">
|
||||
<DependentUpon>ExternalTestResultPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="FileTransferPage.xaml.cs">
|
||||
<DependentUpon>FileTransferPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="SaveLoadEditPage.xaml.cs">
|
||||
<DependentUpon>SaveLoadEditPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TaskStatusDataBindingConverter.cs" />
|
||||
<Compile Include="TaskListExecutionPage.xaml.cs">
|
||||
<DependentUpon>TaskListExecutionPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="obj\AssemblyInfo.cs" />
|
||||
<Compile Include="ResultsPage.xaml.cs">
|
||||
<DependentUpon>ResultsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="MainPage.xaml.cs">
|
||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="WdpPage.xaml.cs">
|
||||
<DependentUpon>WdpPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(IsTFSBuild)' == 'true'">
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(IsTFSBuild)' != 'true'">
|
||||
<AppxManifest Include="DevPackage.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App_TemporaryKey.pfx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\LargeTile.scale-100.png" />
|
||||
<Content Include="Assets\LargeTile.scale-125.png" />
|
||||
<Content Include="Assets\LargeTile.scale-150.png" />
|
||||
<Content Include="Assets\LargeTile.scale-200.png" />
|
||||
<Content Include="Assets\LargeTile.scale-400.png" />
|
||||
<Content Include="Assets\SmallTile.scale-100.png" />
|
||||
<Content Include="Assets\SmallTile.scale-125.png" />
|
||||
<Content Include="Assets\SmallTile.scale-150.png" />
|
||||
<Content Include="Assets\SmallTile.scale-200.png" />
|
||||
<Content Include="Assets\SmallTile.scale-400.png" />
|
||||
<Content Include="Assets\SplashScreen.scale-100.png" />
|
||||
<Content Include="Assets\SplashScreen.scale-125.png" />
|
||||
<Content Include="Assets\SplashScreen.scale-150.png" />
|
||||
<Content Include="Assets\SplashScreen.scale-400.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-100.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-125.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-150.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-400.png" />
|
||||
<Content Include="Assets\Square44x44Logo.altform-unplated_targetsize-16.png" />
|
||||
<Content Include="Assets\Square44x44Logo.altform-unplated_targetsize-256.png" />
|
||||
<Content Include="Assets\Square44x44Logo.altform-unplated_targetsize-32.png" />
|
||||
<Content Include="Assets\Square44x44Logo.altform-unplated_targetsize-48.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-100.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-125.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-150.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-400.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-16.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-256.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-32.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-48.png" />
|
||||
<Content Include="Assets\StoreLogo.scale-100.png" />
|
||||
<Content Include="Assets\StoreLogo.scale-125.png" />
|
||||
<Content Include="Assets\StoreLogo.scale-150.png" />
|
||||
<Content Include="Assets\StoreLogo.scale-200.png" />
|
||||
<Content Include="Assets\StoreLogo.scale-400.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-100.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-125.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-150.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-400.png" />
|
||||
<Content Include="Properties\Default.rd.xml" />
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Page Include="AboutPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="AppsPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="ConsolePage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="ConnectionPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="EditPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="ExternalTestResultPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="FileTransferPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="SaveLoadEditPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="TaskListExecutionPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="ResultsPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="MainPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="WdpPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.9</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ClientLibrary\FactoryOrchestratorClientLibrary.csproj">
|
||||
<Project>{c5fda4e1-e631-47b2-ba5f-6558f8506df7}</Project>
|
||||
<Name>FactoryOrchestratorClientLibrary</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\CoreLibrary\FactoryOrchestratorCoreLibrary.csproj">
|
||||
<Project>{5ed97075-7281-4fa3-8e5f-d4b8d273bdcb}</Project>
|
||||
<Name>FactoryOrchestratorCoreLibrary</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\IpcFramework\IpcServiceFramework.Client\IpcServiceFramework.Client.csproj">
|
||||
<Project>{52ceb7ed-27e4-40df-810f-4971caf0f55e}</Project>
|
||||
<Name>IpcServiceFramework.Client</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\UWPClientLibrary\FactoryOrchestratorUWPClientLibrary.csproj">
|
||||
<Project>{0ed6137e-895f-4941-a136-abb977e1c4ef}</Project>
|
||||
<Name>FactoryOrchestratorUWPClientLibrary</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
|
||||
<Target Name="BeforeBuildPS_VSO" BeforeTargets="Build;CoreCompile;XefVersionResourceCompile;XamlPreCompile" Condition="'$(IsTFSBuild)' == 'true'">
|
||||
<Exec Command="Powershell.exe $(ProjectDir)..\eng\build\SetSourceVersion.ps1 -SrcPath $(ProjectDir)" />
|
||||
</Target>
|
||||
<Target Name="BeforeBuildPS_Local" BeforeTargets="Build;CoreCompile;XefVersionResourceCompile;XamlPreCompile" Condition="'$(IsTFSBuild)' != 'true'">
|
||||
<Exec Command="Powershell.exe $(ProjectDir)..\eng\build\SetSourceVersion.ps1 -SrcPath $(ProjectDir) -MajorMinorOnly" />
|
||||
</Target>
|
||||
<Target Name="AfterBuild" Condition="'$(IsTFSBuild)' == 'true'">
|
||||
<Copy SourceFiles="$(AppxPackageOutput)" DestinationFolder="$(OutputPath)" />
|
||||
</Target>
|
||||
<!-- <PropertyGroup Condition="'$(XES_OUTDIR)' != ''">
|
||||
<PostBuildEvent>robocopy /s $(ProjectDir)AppPackages $(ProjectDir)$(OutDir)AppPackages</PostBuildEvent>
|
||||
</PropertyGroup> -->
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,76 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.FileTransferPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Page.Resources>
|
||||
<Flyout x:Name="ConfirmTransferFlyout" x:Key="ConfirmTransferFlyout">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="HeaderGet" Text="Copy File from Server to Client?" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}" Padding="5"/>
|
||||
<TextBlock x:Name="HeaderSend" Text="Copy File from Client to Server?" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}" Padding="5" Visibility="Collapsed"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="SourceFileHeaderGet" Text="Source File (from Server):" FontWeight="Bold" Padding="5,0"/>
|
||||
<TextBlock x:Name="SourceFileHeaderSend" Text="Source File (from Client):" FontWeight="Bold" Visibility="Collapsed" Padding="5,0"/>
|
||||
<TextBlock x:Name="SourceFileBody" Text=""/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="TargetFileHeaderGet" Text="Destination (on Client):" FontWeight="Bold" Padding="5,0"/>
|
||||
<TextBlock x:Name="TargetFileHeaderSend" Text="Destination (on Server):" FontWeight="Bold" Visibility="Collapsed" Padding="5,0"/>
|
||||
<TextBlock x:Name="TargetFileBody" Text=""/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="CancelCopy" Padding="5" Margin="5" Click="CancelCopy_Click" AutomationProperties.Name="Cancel file copy">
|
||||
<SymbolIcon Symbol="Cancel"/>
|
||||
</Button>
|
||||
<Button x:Name="ConfirmCopy" Padding="5" Margin="5" Click="ConfirmCopy_Click" AutomationProperties.Name="Confirm file copy">
|
||||
<SymbolIcon Symbol="Copy"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<HyperlinkButton Content="Windows 10 (Desktop) only: You must allow file system access for this app! Click here for instructions." NavigateUri="https://support.microsoft.com/en-us/help/4468237/windows-10-file-system-access-and-privacy-microsoft-privacy" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontWeight="bold"/>
|
||||
<TextBlock x:Name="ClientText" Text="Client File: " Grid.Row="2" Grid.Column="1" Style="{StaticResource TitleTextBlockStyle}" HorizontalAlignment="Right" Margin="10,0,0,0" VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="ClientFileTextBox" Grid.Row="2" Grid.Column="2" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="10,0" AutomationProperties.LabeledBy="{Binding ElementName=ClientText}"/>
|
||||
|
||||
<TextBlock x:Name="ServerText" Text="Server File: " Grid.Row="4" Grid.Column="1" Style="{StaticResource TitleTextBlockStyle}" HorizontalAlignment="Right" Margin="10,0,0,0" VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="ServerFileTextBox" Grid.Row="4" Grid.Column="2" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="10,0" AutomationProperties.LabeledBy="{Binding ElementName=ServerText}"/>
|
||||
|
||||
<StackPanel Grid.Row="6" Orientation="Horizontal" Grid.Column="2" HorizontalAlignment="Left">
|
||||
<Button x:Name="SendClientFileButton" Margin="0,0,10,0" Click="SendClientFileButton_Click" FlyoutBase.AttachedFlyout="{StaticResource ConfirmTransferFlyout}" AutomationProperties.LabeledBy="{Binding ElementName=SendText}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="SendText" Text="Send Client File to Server" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="GetServerFileButton" Click="GetServerFileButton_Click" FlyoutBase.AttachedFlyout="{StaticResource ConfirmTransferFlyout}" AutomationProperties.LabeledBy="{Binding ElementName=GetText}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="GetText" Text="Save Server File to Client" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class FileTransferPage : Page
|
||||
{
|
||||
public FileTransferPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void GetServerFileButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((!string.IsNullOrWhiteSpace(ServerFileTextBox.Text)) && (!string.IsNullOrWhiteSpace(ClientFileTextBox.Text)))
|
||||
{
|
||||
var button = sender as Button;
|
||||
HeaderGet.Visibility = Visibility.Visible;
|
||||
HeaderSend.Visibility = Visibility.Collapsed;
|
||||
SourceFileHeaderGet.Visibility = Visibility.Visible;
|
||||
SourceFileHeaderSend.Visibility = Visibility.Collapsed;
|
||||
TargetFileHeaderGet.Visibility = Visibility.Visible;
|
||||
TargetFileHeaderSend.Visibility = Visibility.Collapsed;
|
||||
SourceFileBody.Text = ServerFileTextBox.Text;
|
||||
TargetFileBody.Text = ClientFileTextBox.Text;
|
||||
|
||||
sending = false;
|
||||
|
||||
Flyout.ShowAttachedFlyout(button);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendClientFileButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((!string.IsNullOrWhiteSpace(ServerFileTextBox.Text)) && (!string.IsNullOrWhiteSpace(ClientFileTextBox.Text)))
|
||||
{
|
||||
var button = sender as Button;
|
||||
HeaderGet.Visibility = Visibility.Collapsed;
|
||||
HeaderSend.Visibility = Visibility.Visible;
|
||||
SourceFileHeaderGet.Visibility = Visibility.Collapsed;
|
||||
SourceFileHeaderSend.Visibility = Visibility.Visible;
|
||||
TargetFileHeaderGet.Visibility = Visibility.Collapsed;
|
||||
TargetFileHeaderSend.Visibility = Visibility.Visible;
|
||||
SourceFileBody.Text = ClientFileTextBox.Text;
|
||||
TargetFileBody.Text = ServerFileTextBox.Text;
|
||||
|
||||
sending = true;
|
||||
|
||||
Flyout.ShowAttachedFlyout(button);
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelCopy_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ConfirmTransferFlyout.Hide();
|
||||
}
|
||||
|
||||
private async void ConfirmCopy_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// todo: quality: transfer file in chunks with progress bar & cancel option
|
||||
if (sending)
|
||||
{
|
||||
await Client.SendFileToDevice(ClientFileTextBox.Text, ServerFileTextBox.Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Client.GetFileFromDevice(ServerFileTextBox.Text, ClientFileTextBox.Text);
|
||||
}
|
||||
|
||||
ConfirmTransferFlyout.Hide();
|
||||
}
|
||||
|
||||
private bool sending;
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:pkg="using:Windows.ApplicationModel"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid Grid.Row="0" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||
<TextBlock x:Name="OSVersionHeader" VerticalAlignment="Center" HorizontalAlignment="Left" Style="{StaticResource CaptionTextBlockStyle}" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="OEMVersionHeader" VerticalAlignment="Center" HorizontalAlignment="Left" Style="{StaticResource CaptionTextBlockStyle}" IsTextSelectionEnabled="True"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock x:Name="NetworkName" Style="{StaticResource CaptionTextBlockStyle}" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="NetworkIp" Style="{StaticResource CaptionTextBlockStyle}" IsTextSelectionEnabled="True"/>
|
||||
</StackPanel>
|
||||
<Button x:Name="NetworkButton" AutomationProperties.Name="Network information" Visibility="Visible" Padding="5" Margin="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" FontSize="30" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="NetworkFlyout" Opening="NetworkFlyout_Opening">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource FlyoutPickerTitleTextBlockStyle}" Text="System Ip Addresses" HorizontalAlignment="Center"/>
|
||||
<StackPanel x:Name="NetworkStackPanel" Orientation="Vertical" HorizontalAlignment="Center">
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button x:Name="ExitButton" AutomationProperties.LabeledBy="{Binding ElementName=ExitText}" Padding="5" Margin="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" FontSize="30" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="ExitFlyout" Closed="ExitFlyout_Closed">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="ExitText" TextWrapping="Wrap" Text="Exit Factory Orchestrator?" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}" HorizontalAlignment="Center" Margin="5"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button x:Name="ConfirmExit" Padding="8" Margin="10,5" Click="ConfirmExit_Click" AutomationProperties.LabeledBy="{Binding ElementName=ExitButtonText}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="ExitButtonText" Padding="5,0" TextWrapping="Wrap" Text="Exit"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="ConfirmReboot" Padding="8" Margin="10,5" Click="ConfirmReboot_Click" AutomationProperties.LabeledBy="{Binding ElementName=RebootButtonText}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="RebootButtonText" Padding="5,0" TextWrapping="Wrap" Text="Reboot"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="ConfirmShutdown" Padding="8" Margin="10,5" Click="ConfirmShutdown_Click" AutomationProperties.LabeledBy="{Binding ElementName=ShutdownButtonText}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="ShutdownButtonText" Padding="5,0" TextWrapping="Wrap" Text="Shutdown"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<ProgressBar x:Name="ShutdownProgessBar" IsIndeterminate="True" Visibility="Collapsed" Margin="10"/>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" HorizontalAlignment="Stretch" Margin="30,40,30,0" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="10" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="Header" Grid.Row="0" Text="Factory Orchestrator" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" Style="{StaticResource TitleTextBlockStyle}"/>
|
||||
<TextBlock x:Name="BootTaskWarning" Grid.Row="1" Text="⚠ Service is executing Boot tasks, many funtions are disabled until finished or aborted ⚠" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontStyle="Italic" Style="{StaticResource SubtitleTextBlockStyle}" Visibility="Collapsed" Margin="5"/>
|
||||
<NavigationView x:Name="NavView" Grid.Row="3" PaneDisplayMode="Top" IsBackButtonVisible="Collapsed" IsSettingsVisible="False" ItemInvoked="NavView_ItemInvoked">
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem Tag="run" Content="Run TaskLists" Icon="Play"/>
|
||||
<NavigationViewItem Tag="console" ToolTipService.ToolTip="Non-interactive cmd.exe prompt" AutomationProperties.LabeledBy="{Binding ElementName=CommandPromptText}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="CommandPromptText" Text="Command Prompt" Margin="10, 9"/>
|
||||
</StackPanel>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Tag="apps" Content="UWP Apps" Icon="AllApps" ToolTipService.ToolTip="Launch a UWP app"/>
|
||||
<NavigationViewItem Tag="save" Content="Manage TaskLists" Icon="Edit" ToolTipService.ToolTip="Create or edit a TaskList"/>
|
||||
<NavigationViewItem Tag="files" Content="Transfer Files" Icon="SyncFolder" ToolTipService.ToolTip="Transfer file to/from connected device"/>
|
||||
<NavigationViewItem Tag="wdp" Content="Windows Device Portal" Icon="SwitchApps" ToolTipService.ToolTip="Launch Windows Device Portal"/>
|
||||
<NavigationViewItem Tag="about" Content="About Factory Orchestrator" Icon="Help"/>
|
||||
</NavigationView.MenuItems>
|
||||
<Frame x:Name="ContentFrame" IsTabStop="True" Padding="0, 0, 24, 0"/>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,415 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
Client = ((App)Application.Current).Client;
|
||||
disabledPages = new List<string>();
|
||||
navUpdateSem = new SemaphoreSlim(1, 1);
|
||||
|
||||
// Put Client ipaddress in header
|
||||
Header.Text += Client.IsLocalHost ? " (Local Device)" : $" ({Client.IpAddress.ToString()})";
|
||||
|
||||
// If localhost connection, hide file transfer page
|
||||
if (Client.IsLocalHost)
|
||||
{
|
||||
NavigationViewItem fileItem = (NavigationViewItem)NavView.MenuItems.Where(x => ((NavigationViewItem)x).Tag.ToString() == "files").First();
|
||||
fileItem.Visibility = Visibility.Collapsed;
|
||||
fileItem.IsEnabled = false;
|
||||
var pageMap = navViewPages.Where(x => x.Tag == "files").First();
|
||||
navViewPages.Remove(pageMap);
|
||||
pageMap.Enabled = false;
|
||||
navViewPages.Add(pageMap);
|
||||
}
|
||||
|
||||
// Update visible network information every 7 seconds
|
||||
networkTimer = new System.Timers.Timer(7000);
|
||||
networkTimerIndex = 0;
|
||||
ipAddressSem = new SemaphoreSlim(1, 1);
|
||||
|
||||
// If there was a previous tab loaded, navigate to it
|
||||
lastNavTag = ((App)Application.Current).MainPageLastNavTag;
|
||||
|
||||
// Add handler for ContentFrame navigation.
|
||||
ContentFrame.Navigated += On_ContentFrameNavigated;
|
||||
|
||||
((App)Application.Current).OnServiceDoneExecutingBootTasks += MainPage_OnServiceDoneExecutingBootTasks;
|
||||
((App)Application.Current).OnServiceStart += MainPage_OnServiceStart;
|
||||
}
|
||||
|
||||
private async void MainPage_OnServiceStart()
|
||||
{
|
||||
await navUpdateSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
BootTasksStart();
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
navUpdateSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async void MainPage_OnServiceDoneExecutingBootTasks()
|
||||
{
|
||||
await navUpdateSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
BootTasksDone();
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
navUpdateSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
lastNavTag = e.Parameter as string;
|
||||
Client = ((App)Application.Current).Client;
|
||||
this.Frame.CacheSize = 3;
|
||||
|
||||
await navUpdateSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
// Hide tabs disabled by OEM Customization
|
||||
disabledPages = await Client.GetDisabledPages();
|
||||
foreach (var disabledPage in disabledPages)
|
||||
{
|
||||
foreach (NavigationViewItem item in NavView.MenuItems)
|
||||
{
|
||||
if (item.Tag.ToString() == disabledPage)
|
||||
{
|
||||
item.Visibility = Visibility.Collapsed;
|
||||
item.IsEnabled = false;
|
||||
var pageMap = navViewPages.Where(x => x.Tag == disabledPage).First();
|
||||
navViewPages.Remove(pageMap);
|
||||
pageMap.Enabled = false;
|
||||
navViewPages.Add(pageMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (((App)Application.Current).IsServiceExecutingBootTasks)
|
||||
{
|
||||
// Disable pages that are disallowed during Boot Tasks
|
||||
BootTasksStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enable all pages that are not disabled
|
||||
BootTasksDone();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
navUpdateSem.Release();
|
||||
}
|
||||
|
||||
// Put OS & OEM versions in the footer
|
||||
OEMVersionHeader.Text = "OEM Version: ";
|
||||
OSVersionHeader.Text = "OS Version: ";
|
||||
try
|
||||
{
|
||||
OSVersionHeader.Text += await Client.GetOSVersionString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
OSVersionHeader.Text += $"Could not query OS version!";
|
||||
}
|
||||
try
|
||||
{
|
||||
OEMVersionHeader.Text += await Client.GetOEMVersionString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
OEMVersionHeader.Text += $"Could not query OEM version!";
|
||||
}
|
||||
|
||||
// Configure network information update timer
|
||||
await UpdateIpAddresses();
|
||||
networkTimer.Elapsed += NetworkTimer_Elapsed;
|
||||
// Call elapsed to set initial values
|
||||
NetworkTimer_Elapsed(networkTimer, null);
|
||||
networkTimer.Start();
|
||||
|
||||
if (string.IsNullOrEmpty(lastNavTag))
|
||||
{
|
||||
// NavView doesn't load any page by default, so load home page.
|
||||
NavView.SelectedItem = NavView.MenuItems[0];
|
||||
((App)Application.Current).MainPageLastNavTag = lastNavTag = ((NavigationViewItem)NavView.SelectedItem).Tag.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
NavView.SelectedItem = NavView.MenuItems.Where(x => ((NavigationViewItem)x).Tag.ToString() == lastNavTag).First();
|
||||
}
|
||||
|
||||
if (!disabledPages.Any(str => str.Equals(lastNavTag, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
NavView_Navigate(lastNavTag, null, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
ContentDialog failedAppsDialog = new ContentDialog
|
||||
{
|
||||
|
||||
Title = "Failed to launch " + GetPage(),
|
||||
Content = "Please enable " + GetPage() + " and try again.",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await failedAppsDialog.ShowAsync();
|
||||
}
|
||||
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call when boot tasks start. Disables pages not allowed during boot.
|
||||
/// </summary>
|
||||
private void BootTasksStart()
|
||||
{
|
||||
BootTaskWarning.Visibility = Visibility.Visible;
|
||||
var pagesToDisable = navViewPages.Where(x => (x.AllowedDuringBoot == false) && (x.Enabled == true)).ToArray();
|
||||
for (int i = 0; i < pagesToDisable.Count(); i++)
|
||||
{
|
||||
var pageMap = pagesToDisable[i];
|
||||
var item = (NavigationViewItem)NavView.MenuItems.Where(x => ((NavigationViewItem)x).Tag.ToString() == pageMap.Tag).First();
|
||||
navViewPages.Remove(pageMap);
|
||||
pageMap.Enabled = false;
|
||||
navViewPages.Add(pageMap);
|
||||
item.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call when boot tasks complete. Enables all non-disabled pages.
|
||||
/// </summary>
|
||||
private void BootTasksDone()
|
||||
{
|
||||
BootTaskWarning.Visibility = Visibility.Collapsed;
|
||||
|
||||
var pagesToEnable = navViewPages.Where(x => (x.AllowedDuringBoot == false) && (x.Enabled == false) && (!disabledPages.Contains(x.Tag))).ToArray();
|
||||
for (int i = 0; i < pagesToEnable.Count(); i++)
|
||||
{
|
||||
var pageMap = pagesToEnable[i];
|
||||
var item = (NavigationViewItem)NavView.MenuItems.Where(x => ((NavigationViewItem)x).Tag.ToString() == pageMap.Tag).First();
|
||||
navViewPages.Remove(pageMap);
|
||||
pageMap.Enabled = true;
|
||||
navViewPages.Add(pageMap);
|
||||
item.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPage()
|
||||
{
|
||||
string Page = "";
|
||||
switch (lastNavTag)
|
||||
{
|
||||
case "wdp":
|
||||
Page = "Windows Device Portal";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return Page;
|
||||
}
|
||||
|
||||
private void On_ContentFrameNavigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
if (ContentFrame.SourcePageType != null)
|
||||
{
|
||||
var item = navViewPages.FirstOrDefault(p => (p.Page == e.SourcePageType));
|
||||
|
||||
NavView.SelectedItem = NavView.MenuItems.OfType<NavigationViewItem>().
|
||||
First(n => n.Tag.Equals(item.Tag));
|
||||
|
||||
if (item.Tag == "console")
|
||||
{
|
||||
NavView.Header = "Command Prompt";
|
||||
}
|
||||
else
|
||||
{
|
||||
NavView.Header = ((NavigationViewItem)NavView.SelectedItem)?.Content?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
|
||||
{
|
||||
if (args.InvokedItemContainer != null)
|
||||
{
|
||||
var navItemTag = args.InvokedItemContainer.Tag.ToString();
|
||||
NavView_Navigate(navItemTag, args.RecommendedNavigationTransitionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void NavView_Navigate(string navItemTag, NavigationTransitionInfo recommendedNavigationTransitionInfo, NavigationEventArgs e = null)
|
||||
{
|
||||
((App)Application.Current).MainPageLastNavTag = lastNavTag = navItemTag;
|
||||
Type page = null;
|
||||
var pageMap = navViewPages.FirstOrDefault(p => p.Tag.Equals(navItemTag));
|
||||
|
||||
if (pageMap.Enabled)
|
||||
{
|
||||
// Dont navigate unless the page is enabled
|
||||
page = pageMap.Page;
|
||||
}
|
||||
|
||||
// Get the page type before navigation so you can prevent duplicate entries in the backstack.
|
||||
var preNavPageType = ContentFrame.CurrentSourcePageType;
|
||||
|
||||
// Only navigate if the selected page isn't currently loaded or if we came from another full page view.
|
||||
if (!(page is null) && (!Type.Equals(preNavPageType, page) || e != null))
|
||||
{
|
||||
ContentFrame.Navigate(page, this.Frame, recommendedNavigationTransitionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfirmExit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((App)Application.Current).Exit();
|
||||
}
|
||||
private void ConfirmReboot_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Client.RebootDevice(5);
|
||||
ShutdownProgessBar.Visibility = Visibility.Visible;
|
||||
ConfirmReboot.IsEnabled = false;
|
||||
ConfirmShutdown.IsEnabled = false;
|
||||
ConfirmExit.IsEnabled = false;
|
||||
}
|
||||
|
||||
private void ConfirmShutdown_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Client.ShutdownDevice(5);
|
||||
ShutdownProgessBar.Visibility = Visibility.Visible;
|
||||
ConfirmReboot.IsEnabled = false;
|
||||
ConfirmShutdown.IsEnabled = false;
|
||||
ConfirmExit.IsEnabled = false;
|
||||
}
|
||||
private async void NetworkFlyout_Opening(object sender, object e)
|
||||
{
|
||||
await UpdateIpAddresses();
|
||||
await ipAddressSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
NetworkStackPanel.Children.Clear();
|
||||
|
||||
foreach (var ipAndNic in ipAddresses)
|
||||
{
|
||||
NetworkStackPanel.Children.Add(new TextBlock()
|
||||
{
|
||||
Text = $"{ipAndNic.Item2} : {ipAndNic.Item1}",
|
||||
IsTextSelectionEnabled = true
|
||||
});
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ipAddressSem.Release();
|
||||
}
|
||||
}
|
||||
private void ExitFlyout_Closed(object sender, object e)
|
||||
{
|
||||
ConfirmExit.IsEnabled = true;
|
||||
ConfirmReboot.IsEnabled = true;
|
||||
ConfirmShutdown.IsEnabled = true;
|
||||
ShutdownProgessBar.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async void NetworkTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
await UpdateIpAddresses();
|
||||
await ipAddressSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
if (ipAddresses.Count == 0)
|
||||
{
|
||||
NetworkIp.Text = "";
|
||||
NetworkName.Text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (ipAddresses.Count <= networkTimerIndex)
|
||||
{
|
||||
networkTimerIndex = 0;
|
||||
}
|
||||
|
||||
var ipAndName = ipAddresses[networkTimerIndex++];
|
||||
NetworkIp.Text = ipAndName.Item1;
|
||||
NetworkName.Text = ipAndName.Item2;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
ipAddressSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateIpAddresses()
|
||||
{
|
||||
await ipAddressSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
ipAddresses = await Client.GetIpAddressesAndNicNames();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ipAddressSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private string lastNavTag;
|
||||
private List<(string Tag, Type Page, bool Enabled, bool AllowedDuringBoot)> navViewPages = new List<(string Tag, Type Page, bool Enabled, bool AllowedDuringBoot)>
|
||||
{
|
||||
("run", typeof(TaskListExecutionPage), true, true),
|
||||
("console", typeof(ConsolePage), true, false),
|
||||
("apps", typeof(AppsPage), true, false),
|
||||
("save", typeof(SaveLoadEditPage), true, false),
|
||||
("files", typeof(FileTransferPage), true, false),
|
||||
("wdp", typeof(WdpPage), true, true),
|
||||
("about", typeof(AboutPage), true, true)
|
||||
};
|
||||
private List<string> disabledPages;
|
||||
private SemaphoreSlim navUpdateSem;
|
||||
|
||||
private FactoryOrchestratorUWPClient Client;
|
||||
private System.Timers.Timer networkTimer;
|
||||
private int networkTimerIndex;
|
||||
private List<Tuple<string, string>> ipAddresses;
|
||||
private SemaphoreSlim ipAddressSem;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap mp rescap">
|
||||
<Identity Name="Microsoft.FactoryOrchestratorApp" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.0.0.0" />
|
||||
<mp:PhoneIdentity PhoneProductId="598c7e0f-acaf-4866-90a5-de8ad458aa53" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
||||
<Properties>
|
||||
<DisplayName>Factory Orchestrator</DisplayName>
|
||||
<PublisherDisplayName>Microsoft</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
</Resources>
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="FactoryOrchestratorApp.App">
|
||||
<uap:VisualElements DisplayName="Factory Orchestrator" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Factory Orchestrator UWP" BackgroundColor="#000000">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square310x310Logo="Assets\LargeTile.png" Square71x71Logo="Assets\SmallTile.png">
|
||||
</uap:DefaultTile>
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#000000" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="fo">
|
||||
<uap:DisplayName>fo</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<Capability Name="internetClientServer" />
|
||||
<Capability Name="privateNetworkClientServer" />
|
||||
<rescap:Capability Name="packageQuery"/>
|
||||
<rescap:Capability Name="extendedExecutionUnconstrained"/>
|
||||
<rescap:Capability Name="broadFileSystemAccess"/>
|
||||
<rescap:Capability Name="confirmAppClose"/>
|
||||
</Capabilities>
|
||||
</Package>
|
|
@ -0,0 +1,31 @@
|
|||
<!--
|
||||
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
|
||||
developers. However, you can modify these parameters to modify the behavior of the .NET Native
|
||||
optimizer.
|
||||
|
||||
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
|
||||
|
||||
To fully enable reflection for App1.MyClass and all of its public/private members
|
||||
<Type Name="App1.MyClass" Dynamic="Required All"/>
|
||||
|
||||
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
|
||||
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
|
||||
|
||||
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
|
||||
<Namespace Name="DataClasses.ViewModels" Serialize="All" />
|
||||
-->
|
||||
|
||||
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
|
||||
<Application>
|
||||
<!--
|
||||
An Assembly element with Name="*Application*" applies to all assemblies in
|
||||
the application package. The asterisks are not wildcards.
|
||||
-->
|
||||
<Assembly Name="*Application*" Dynamic="Required All" />
|
||||
|
||||
|
||||
<!-- Add your application specific runtime directives here. -->
|
||||
|
||||
|
||||
</Application>
|
||||
</Directives>
|
|
@ -0,0 +1,85 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.ResultsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" HorizontalAlignment="Stretch" Margin="50,50,50,50" VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Button x:Name="BackButton" Grid.Row="0" Grid.Column="0" Click="Back_Click" Style="{StaticResource NavigationBackButtonNormalStyle}" AutomationProperties.Name="Go Back"/>
|
||||
<StackPanel x:Name="HeaderStack" Grid.Row="0" Grid.Column="1" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Center" Orientation="Horizontal" >
|
||||
<Button x:Name="PreviousRunButton" Click="PreviousRunButton_Click" Visibility="Visible" IsEnabled="False" AutomationProperties.LabeledBy="{Binding ElementName=PrevText}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<SymbolIcon Symbol="Back" />
|
||||
<TextBlock x:Name="PrevText" Padding="5,0" TextWrapping="Wrap" Text="Previous Run"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock x:Name="TestHeader" Text="" Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" Style="{StaticResource TitleTextBlockStyle}" IsTextSelectionEnabled="True" />
|
||||
<Button x:Name="NextRunButton" Click="NextRunButton_Click" Visibility="Visible" IsEnabled="False" AutomationProperties.LabeledBy="{Binding ElementName=NextText}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<SymbolIcon Symbol="Forward" />
|
||||
<TextBlock x:Name="NextText" Padding="5,0" TextWrapping="Wrap" Text="Next Run"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="TaskPathStack" Grid.Row="1" Grid.Column="1" Margin="0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TaskPathConst" Text="Path:" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5"/>
|
||||
<TextBlock x:Name="TaskPath" Text="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" IsTextSelectionEnabled="True"/>
|
||||
</StackPanel>
|
||||
<Grid x:Name="TestArgsStack" Grid.Row="2" Grid.Column="1" Margin="0" VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="ArgsConst" Text="Arguments:" Grid.Column="0" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" />
|
||||
<TextBlock x:Name="Args" Text="" Grid.Column="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Margin="5" TextWrapping="NoWrap" IsTextSelectionEnabled="True"/>
|
||||
</Grid>
|
||||
<StackPanel x:Name="TaskRunStack" Grid.Row="3" Grid.Column="1" Margin="0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TaskGuidConst" Text="Task Guid:" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5"/>
|
||||
<TextBlock x:Name="TaskGuid" Text="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="TaskRunGuidConst" Text="TaskRun Guid:" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" Visibility="Collapsed"/>
|
||||
<TextBlock x:Name="TaskRunGuid" Text="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" IsTextSelectionEnabled="True" Visibility="Collapsed"/>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="TestResultSummaryStack" Grid.Row="4" Grid.Column="1" Margin="0" VerticalAlignment="Center" Orientation="Horizontal" >
|
||||
<TextBlock x:Name="OverallTestResultConst" Text="Status:" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5"/>
|
||||
<TextBlock x:Name="OverallTestResult" Text="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="ExitCodeConst" Text="Exit Code:" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5"/>
|
||||
<TextBlock x:Name="ExitCode" Text="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="TaefPassedCasesConst" Text="Passed Test Cases:" FontWeight="Bold" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed"/>
|
||||
<TextBlock x:Name="TaefPassedCases" Text="" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="TaefFailedCasesConst" Text="Failed Test Cases:" FontWeight="Bold" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed"/>
|
||||
<TextBlock x:Name="TaefFailedCases" Text="" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="TaefWarningCasesConst" Text="Test Cases with Warnings:" FontWeight="Bold" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed"/>
|
||||
<TextBlock x:Name="TaefWarningCases" Text="" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="LastTimeRunConst" Text="Started at:" FontWeight="Bold" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed"/>
|
||||
<TextBlock x:Name="LastTimeRun" Text="" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed" IsTextSelectionEnabled="True"/>
|
||||
<TextBlock x:Name="RunTimeConst" Text="Task Run Time:" FontWeight="Bold" VerticalAlignment="Bottom" Margin="5" HorizontalAlignment="Left" Visibility="Collapsed"/>
|
||||
<TextBlock x:Name="RunTime" Text="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" Visibility="Collapsed" IsTextSelectionEnabled="True"/>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="TestResultSummaryStack2" Grid.Row="5" Grid.Column="1" Margin="0" VerticalAlignment="Center" Orientation="Horizontal" >
|
||||
<TextBlock x:Name="LogPathConst" Text="Log:" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5"/>
|
||||
<TextBlock x:Name="LogPath" Text="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="5" IsTextSelectionEnabled="True"/>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="OutputConst" Grid.Column="1" Grid.Row="6" FontWeight="Bold" VerticalAlignment="Bottom" Padding="5,5,0,5" HorizontalAlignment="Left" Text="Task Output:"/>
|
||||
<ScrollViewer x:Name="ScrollView" HorizontalScrollBarVisibility="Auto" Grid.Column="1" Grid.Row="7" Margin="5" ViewChanged="ScrollView_ViewChanged">
|
||||
<StackPanel x:Name="OutputStack" VerticalAlignment="Top" Orientation="Vertical" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,572 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class ResultsPage : Page
|
||||
{
|
||||
public ResultsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.NavigationCacheMode = NavigationCacheMode.Disabled;
|
||||
_isEmbedded = false;
|
||||
_updateSem = new SemaphoreSlim(1, 1);
|
||||
_taskRunPollLock = new object();
|
||||
_isBootTask = false;
|
||||
}
|
||||
|
||||
public async Task SetupForTask(TaskBase task)
|
||||
{
|
||||
if (((App)Application.Current).IsServiceExecutingBootTasks)
|
||||
{
|
||||
_isBootTask = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isBootTask = false;
|
||||
}
|
||||
|
||||
Client = ((App)Application.Current).Client;
|
||||
StopPolling();
|
||||
await ClearOutput();
|
||||
_taskRunPoller = null;
|
||||
_test = task;
|
||||
FollowOutput = true;
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
_taskPoller = new ServerPoller(_test.Guid, typeof(TaskBase), 5000);
|
||||
_taskPoller.OnUpdatedObject += OnUpdatedTestAsync;
|
||||
_taskPoller.OnException += OnPollingException;
|
||||
_taskPoller.StartPolling(Client);
|
||||
}
|
||||
|
||||
UpdateTaskRunNav(null);
|
||||
}
|
||||
|
||||
public void StopPolling()
|
||||
{
|
||||
if (_taskPoller != null)
|
||||
{
|
||||
_taskPoller.StopPolling();
|
||||
}
|
||||
if (_taskRunPoller != null)
|
||||
{
|
||||
_taskRunPoller.StopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
await SetupForTask(e.Parameter as TaskBase);
|
||||
BackButton.IsEnabled = this.Frame.CanGoBack;
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
StopPolling();
|
||||
}
|
||||
|
||||
private async void OnUpdatedTestAsync(object source, ServerPollerEventArgs e)
|
||||
{
|
||||
_test = (TaskBase)e.Result;
|
||||
if ((_test != null) && (_taskRunPoller == null))
|
||||
{
|
||||
if (_test.LatestTaskRunTimeFinished != null)
|
||||
{
|
||||
// Test is already finished, don't scroll to end of output
|
||||
FollowOutput = false;
|
||||
}
|
||||
|
||||
if (!TryCreateTaskRunPoller(_test.LatestTaskRunGuid))
|
||||
{
|
||||
// Set task status to not run
|
||||
OverallTestResult.Text = "❔ Not Run";
|
||||
}
|
||||
}
|
||||
else if (_test.TaskRunGuids.Count == 0)
|
||||
{
|
||||
// TaskRuns were deleted while we were looking at the results
|
||||
_taskRunPoller.StopPolling();
|
||||
_selectedRun = null;
|
||||
_taskRunPoller = null;
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
|
||||
{
|
||||
await ClearOutput();
|
||||
UpdateTaskRunNav(_selectedRun);
|
||||
TaskRunGuid.Visibility = Visibility.Collapsed;
|
||||
TaskRunGuidConst.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
}
|
||||
|
||||
if (_test != null)
|
||||
{
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
UpdateTaskRunNav(_selectedRun);
|
||||
CreateHeader();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnUpdatedTaskRunAsync(object source, ServerPollerEventArgs e)
|
||||
{
|
||||
var newRun = (TaskRun)e.Result;
|
||||
|
||||
if (newRun != null)
|
||||
{
|
||||
if (_selectedRun == null || newRun.Guid != _selectedRun.Guid)
|
||||
{
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
|
||||
{
|
||||
await ClearOutput();
|
||||
UpdateTaskRunNav(newRun);
|
||||
});
|
||||
}
|
||||
|
||||
_selectedRun = newRun;
|
||||
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
UpdateResultsSummary();
|
||||
});
|
||||
|
||||
|
||||
await _updateSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
while (lastOutput != _selectedRun.TaskOutput.Count)
|
||||
{
|
||||
var blocks = PrepareOutput();
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
UpdateOutput(blocks);
|
||||
});
|
||||
}
|
||||
|
||||
if (FollowOutput)
|
||||
{
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
// Scroll to end
|
||||
ScrollView.ViewChanged -= ScrollView_ViewChanged;
|
||||
ScrollView.ViewChanged -= Temporary_ViewChanged;
|
||||
ScrollView.ViewChanged += Temporary_ViewChanged;
|
||||
ScrollView.ChangeView(null, ScrollView.ScrollableHeight, null);
|
||||
});
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updateSem.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearOutput()
|
||||
{
|
||||
await _updateSem.WaitAsync();
|
||||
try
|
||||
{
|
||||
lastOutput = 0;
|
||||
OutputStack.Children.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updateSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void Back_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
On_BackRequested();
|
||||
}
|
||||
|
||||
private bool On_BackRequested()
|
||||
{
|
||||
if (this.Frame.CanGoBack)
|
||||
{
|
||||
this.Frame.GoBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void BackInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
|
||||
{
|
||||
On_BackRequested();
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void CreateHeader()
|
||||
{
|
||||
TestHeader.Text = _test.Name;
|
||||
TaskGuid.Text = _test.Guid.ToString();
|
||||
|
||||
if (_test.Path != null)
|
||||
{
|
||||
TaskPath.Text = _test.Path;
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskPath.Text = "";
|
||||
}
|
||||
|
||||
if (_test.Arguments != null)
|
||||
{
|
||||
Args.Text = _test.Arguments.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Args.Text = "";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateResultsSummary()
|
||||
{
|
||||
var children = TestResultSummaryStack.Children;
|
||||
switch (_selectedRun.TaskStatus)
|
||||
{
|
||||
case TaskStatus.Passed:
|
||||
OverallTestResult.Text = "✔ Passed";
|
||||
break;
|
||||
case TaskStatus.Failed:
|
||||
OverallTestResult.Text = "❌ Failed";
|
||||
break;
|
||||
case TaskStatus.Running:
|
||||
OverallTestResult.Text = "▶ Running";
|
||||
break;
|
||||
case TaskStatus.NotRun:
|
||||
OverallTestResult.Text = "❔ Not Run";
|
||||
break;
|
||||
case TaskStatus.Aborted:
|
||||
OverallTestResult.Text = "⛔ Aborted";
|
||||
break;
|
||||
case TaskStatus.Timeout:
|
||||
OverallTestResult.Text = "⏱ Timed-out";
|
||||
break;
|
||||
case TaskStatus.RunPending:
|
||||
OverallTestResult.Text = "❔ Run Pending";
|
||||
break;
|
||||
default:
|
||||
OverallTestResult.Text = "❔ Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
switch (_selectedRun.TaskStatus)
|
||||
{
|
||||
case TaskStatus.Passed:
|
||||
case TaskStatus.Failed:
|
||||
ExitCode.Text = _selectedRun.ExitCode.ToString();
|
||||
ExitCodeConst.Visibility = Visibility.Visible;
|
||||
ExitCode.Visibility = Visibility.Visible;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (_selectedRun.TimeStarted != null)
|
||||
{
|
||||
LastTimeRun.Text = _selectedRun.TimeStarted.ToString();
|
||||
LastTimeRunConst.Visibility = Visibility.Visible;
|
||||
LastTimeRun.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
if (_selectedRun.RunTime != null)
|
||||
{
|
||||
RunTime.Text = _selectedRun.RunTime.ToString();
|
||||
RunTimeConst.Visibility = Visibility.Visible;
|
||||
RunTime.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
if (_selectedRun.LogFilePath != null)
|
||||
{
|
||||
LogPath.Text = _selectedRun.LogFilePath.ToString();
|
||||
LogPathConst.Visibility = Visibility.Visible;
|
||||
LogPath.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
TaskRunGuid.Text = _selectedRun.Guid.ToString();
|
||||
TaskRunGuid.Visibility = Visibility.Visible;
|
||||
TaskRunGuidConst.Visibility = Visibility.Visible;
|
||||
|
||||
// TODO: Feature: Wire up test cases when we track those for TAEF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates UI with latest console output
|
||||
/// </summary>
|
||||
private List<(string text, bool isError)> PrepareOutput()
|
||||
{
|
||||
List<(string text, bool isError)> ret = new List<(string text, bool isError)>();
|
||||
|
||||
var endCount = Math.Min(_selectedRun.TaskOutput.Count, lastOutput + 500);
|
||||
string text = "";
|
||||
bool errorBlock = false;
|
||||
|
||||
for (int i = lastOutput; i < endCount; i++)
|
||||
{
|
||||
if (_selectedRun.TaskOutput[i] != null)
|
||||
{
|
||||
if (errorBlock && _selectedRun.TaskOutput[i].StartsWith("ERROR: "))
|
||||
{
|
||||
// Append error text
|
||||
text += _selectedRun.TaskOutput[i];
|
||||
errorBlock = true;
|
||||
}
|
||||
else if (errorBlock)
|
||||
{
|
||||
// Done with error text, write out the error text and start again
|
||||
var tupl = (text, true);
|
||||
ret.Add(tupl);
|
||||
|
||||
text = _selectedRun.TaskOutput[i];
|
||||
errorBlock = false;
|
||||
}
|
||||
else if (!errorBlock && _selectedRun.TaskOutput[i].StartsWith("ERROR: "))
|
||||
{
|
||||
// Done with normal text, write out the normal text and start again
|
||||
var tupl = (text, false);
|
||||
ret.Add(tupl);
|
||||
|
||||
text = _selectedRun.TaskOutput[i];
|
||||
errorBlock = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append normal text
|
||||
text += _selectedRun.TaskOutput[i];
|
||||
errorBlock = false;
|
||||
}
|
||||
|
||||
if (i != (endCount - 1))
|
||||
{
|
||||
text += System.Environment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastOutput = endCount;
|
||||
|
||||
if (!String.IsNullOrEmpty(text))
|
||||
{
|
||||
if (errorBlock)
|
||||
{
|
||||
var tupl = (text, true);
|
||||
ret.Add(tupl);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tupl = (text, false);
|
||||
ret.Add(tupl);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates UI with latest console output
|
||||
/// </summary>
|
||||
private void UpdateOutput(List<(string text, bool isError)> blocks)
|
||||
{
|
||||
foreach (var block in blocks)
|
||||
{
|
||||
var textBlock = new TextBlock()
|
||||
{
|
||||
Text = block.text,
|
||||
IsTextSelectionEnabled = true
|
||||
};
|
||||
|
||||
if (block.isError)
|
||||
{
|
||||
textBlock.FontWeight = Windows.UI.Text.FontWeights.Bold;
|
||||
textBlock.Foreground = new SolidColorBrush(Windows.UI.Colors.Red);
|
||||
}
|
||||
|
||||
OutputStack.Children.Add(textBlock);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryCreateTaskRunPoller(Guid? taskRunGuid)
|
||||
{
|
||||
lock (_taskRunPollLock)
|
||||
{
|
||||
if (taskRunGuid != null)
|
||||
{
|
||||
if (_taskRunPoller != null)
|
||||
{
|
||||
if (_taskRunPoller.PollingGuid == taskRunGuid)
|
||||
{
|
||||
if (!_taskRunPoller.IsPolling)
|
||||
{
|
||||
_taskRunPoller.StartPolling(Client);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_taskRunPoller.StopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
_taskRunPoller = new ServerPoller((Guid)taskRunGuid, typeof(TaskRun), 1000);
|
||||
_taskRunPoller.OnUpdatedObject += OnUpdatedTaskRunAsync;
|
||||
_taskRunPoller.OnException += OnPollingException;
|
||||
_taskRunPoller.StartPolling(Client);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateTaskRunNav(TaskRun run)
|
||||
{
|
||||
if ((run == null) || (_test.TaskRunGuids.Count <= 1))
|
||||
{
|
||||
NextRunButton.IsEnabled = false;
|
||||
PreviousRunButton.IsEnabled = false;
|
||||
}
|
||||
else if (run != null)
|
||||
{
|
||||
if (_test.TaskRunGuids.IndexOf(run.Guid) < _test.TaskRunGuids.Count - 1)
|
||||
{
|
||||
NextRunButton.IsEnabled = true;
|
||||
}
|
||||
if (_test.TaskRunGuids.IndexOf(run.Guid) > 0)
|
||||
{
|
||||
PreviousRunButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviousRunButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Disable Navi until we get a new TaskRun object
|
||||
NextRunButton.IsEnabled = false;
|
||||
PreviousRunButton.IsEnabled = false;
|
||||
TryCreateTaskRunPoller(_test.TaskRunGuids[_test.TaskRunGuids.IndexOf(_selectedRun.Guid) - 1]);
|
||||
}
|
||||
|
||||
private void NextRunButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NextRunButton.IsEnabled = false;
|
||||
PreviousRunButton.IsEnabled = false;
|
||||
TryCreateTaskRunPoller(_test.TaskRunGuids[_test.TaskRunGuids.IndexOf(_selectedRun.Guid) + 1]);
|
||||
}
|
||||
|
||||
private void ScrollView_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
|
||||
{
|
||||
FollowOutput = false;
|
||||
|
||||
if (e.IsIntermediate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ScrollView.ScrollableHeight == ScrollView.VerticalOffset)
|
||||
{
|
||||
FollowOutput = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Temporary_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
|
||||
{
|
||||
if (e.IsIntermediate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ScrollView.ViewChanged -= Temporary_ViewChanged;
|
||||
ScrollView.ViewChanged -= ScrollView_ViewChanged;
|
||||
ScrollView.ViewChanged += ScrollView_ViewChanged;
|
||||
|
||||
ScrollView.HorizontalScrollMode = ScrollMode.Enabled;
|
||||
ScrollView.VerticalScrollMode = ScrollMode.Enabled;
|
||||
ScrollView.ZoomMode = ZoomMode.Enabled;
|
||||
}
|
||||
|
||||
private async void OnPollingException(object source, ServerPollerExceptionHandlerArgs e)
|
||||
{
|
||||
bool handled = false;
|
||||
if (e.Exception.GetType() == typeof(FactoryOrchestratorUnkownGuidException))
|
||||
{
|
||||
if (_isBootTask)
|
||||
{
|
||||
// This was a Boot Task, but Boot Tasks are completed so the GUID is invalid. Ignore exception, stop polling.
|
||||
if (!await Client.IsExecutingBootTasks())
|
||||
{
|
||||
handled = true;
|
||||
StopPolling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass to global exception handler
|
||||
if (!handled)
|
||||
{
|
||||
((App)Application.Current).OnServerPollerException(source, e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmbedded
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isEmbedded;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == true)
|
||||
{
|
||||
BackButton.Visibility = Visibility.Collapsed;
|
||||
PreviousRunButton.Visibility = Visibility.Collapsed;
|
||||
NextRunButton.Visibility = Visibility.Collapsed;
|
||||
TestHeader.Visibility = Visibility.Collapsed;
|
||||
_isEmbedded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
BackButton.Visibility = Visibility.Visible;
|
||||
PreviousRunButton.Visibility = Visibility.Visible;
|
||||
NextRunButton.Visibility = Visibility.Visible;
|
||||
TestHeader.Visibility = Visibility.Visible;
|
||||
_isEmbedded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FollowOutput { get; set; }
|
||||
|
||||
private TaskBase _test;
|
||||
private TaskRun _selectedRun;
|
||||
private ServerPoller _taskRunPoller;
|
||||
private ServerPoller _taskPoller;
|
||||
private object _taskRunPollLock;
|
||||
private SemaphoreSlim _updateSem;
|
||||
private int lastOutput;
|
||||
private bool _isEmbedded;
|
||||
private bool _isBootTask;
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// A UI helper class to select the correct DataTemplate to use for TaskListsView items.
|
||||
/// </summary>
|
||||
public class ResultsViewSelector : DataTemplateSelector
|
||||
{
|
||||
// These templates are set by TaskListExecutionPage.xaml which instantiates the ActiveTaskListSelector.
|
||||
public DataTemplate Normal { get; set; }
|
||||
public DataTemplate RetryButtonShown { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the template to use for a given TaskListSummaryWithTemplate.
|
||||
/// Called every time a list item in TaskListsView changes.
|
||||
/// </summary>
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
FrameworkElement element = container as FrameworkElement;
|
||||
DataTemplate dataTemplate = null;
|
||||
try
|
||||
{
|
||||
if (element != null && item != null && item is TaskBaseWithTemplate)
|
||||
{
|
||||
var taskWithTemplate = (TaskBaseWithTemplate)item;
|
||||
|
||||
switch (taskWithTemplate.Template)
|
||||
{
|
||||
case TaskViewTemplate.WithRetryButton:
|
||||
dataTemplate = RetryButtonShown;
|
||||
break;
|
||||
default:
|
||||
dataTemplate = Normal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e.AllExceptionsToString());
|
||||
dataTemplate = Normal;
|
||||
}
|
||||
|
||||
return dataTemplate;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.SaveLoadEditPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:core="using:Microsoft.FactoryOrchestrator.Core"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
<Flyout x:Name="SaveFlyout" x:Key="SaveFlyout" Closing="SaveFlyout_Closing">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="SaveFlyoutTextHeader" Text="File to save as:" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}" Padding="5"/>
|
||||
<TextBox x:Name="SaveFlyoutUserPath" Text="" KeyDown="ConfirmSave_KeyDown" AutomationProperties.LabeledBy="{Binding ElementName=SaveFlyoutTextHeader}"/>
|
||||
<TextBlock x:Name="SaveFlyoutText1" Text=".xml will be automattically appended to the filename"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="CancelSave" Padding="5" Margin="5" Click="CancelSave_Click" AutomationProperties.Name="Cancel Save">
|
||||
<SymbolIcon Symbol="Cancel"/>
|
||||
</Button>
|
||||
<Button x:Name="ConfirmSave" Padding="5" Margin="5" Click="ConfirmSave_Click" AutomationProperties.Name="Confirm Save">
|
||||
<SymbolIcon Symbol="Save"/>
|
||||
</Button>
|
||||
<ProgressBar x:Name="SaveProgressBar" Margin="5" IsIndeterminate="True" Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
<Flyout x:Name="LoadFlyout" x:Key="LoadFlyout" Closing="LoadFlyout_Closing">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="LoadFlyoutTextHeader" Text="" Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}" Padding="5"/>
|
||||
<TextBox x:Name="LoadFlyoutUserPath" Text="" KeyDown="ConfirmLoad_KeyDown" AutomationProperties.LabeledBy="{Binding ElementName=LoadFlyoutTextHeader}"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="CancelLoad" Padding="5" Margin="5" Click="CancelLoad_Click" AutomationProperties.Name="Cancel load">
|
||||
<SymbolIcon Symbol="Cancel"/>
|
||||
</Button>
|
||||
<Button x:Name="ConfirmLoad" Padding="5" Margin="5" Click="ConfirmLoad_Click" AutomationProperties.Name="Confirm load">
|
||||
<SymbolIcon Symbol="Accept"/>
|
||||
</Button>
|
||||
<ProgressBar x:Name="LoadProgressBar" Margin="5" IsIndeterminate="True" Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock x:Name="LoadFolderText" Text="Load Folder as TaskList (exe, cmd, ps1, TAEF): " Grid.Row="0" Grid.Column="0" Style="{StaticResource TitleTextBlockStyle}" HorizontalAlignment="Right" Margin="10,10,0,0" VerticalAlignment="Center"/>
|
||||
<Button x:Name="LoadFolderButton" Grid.Row="0" Grid.Column="1" Margin="10,0" HorizontalAlignment="Left" VerticalAlignment="Center" Click="LoadFolderButton_Click" FlyoutBase.AttachedFlyout="{StaticResource LoadFlyout}" AutomationProperties.LabeledBy="{Binding ElementName=LoadFolderText}">
|
||||
<SymbolIcon Symbol="OpenLocal"/>
|
||||
</Button>
|
||||
|
||||
<TextBlock x:Name="ImportText" Text="Import FactoryOrchestratorXML File: " Grid.Row="2" Grid.Column="0" Style="{StaticResource TitleTextBlockStyle}" Margin="10,0" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
<Button x:Name="LoadFileButton" Grid.Row="2" Grid.Column="1" Margin="10,0" HorizontalAlignment="Left" VerticalAlignment="Center" Click="LoadFileButton_Click" FlyoutBase.AttachedFlyout="{StaticResource LoadFlyout}" AutomationProperties.LabeledBy="{Binding ElementName=ImportText}">
|
||||
<SymbolIcon Symbol="OpenFile"/>
|
||||
</Button>
|
||||
|
||||
<TextBlock x:Name="NewText" Text="Create new TaskList: " Grid.Row="4" Grid.Column="0" Style="{StaticResource TitleTextBlockStyle}" Margin="10,0" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
<Button x:Name="NewListButton" Grid.Row="4" Grid.Column="1" Margin="10,0" HorizontalAlignment="Left" VerticalAlignment="Center" Click="NewListButton_Click" FlyoutBase.AttachedFlyout="{StaticResource LoadFlyout}" AutomationProperties.LabeledBy="{Binding ElementName=NewText}">
|
||||
<SymbolIcon Symbol="Add"/>
|
||||
</Button>
|
||||
|
||||
<StackPanel Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10,0">
|
||||
<TextBlock Text="Existing TaskLists:" Style="{StaticResource TitleTextBlockStyle}" />
|
||||
<TextBlock Text="Existing TaskLists are loaded every boot unless deleted. They can also be manually exported to a FactoryOrchestratorXML file." Style="{StaticResource BodyTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
<ListView x:Name="TaskListsView" Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="3" SelectionMode="None" AllowDrop="True" IsItemClickEnabled="False" CanDragItems="True" CanReorderItems="True" IsSwipeEnabled="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ScrollViewer.VerticalScrollMode="Enabled" ItemsSource="{x:Bind TaskListCollection, Mode=TwoWay}" DragItemsCompleted="TaskListsView_DragItemsCompleted">
|
||||
<ListView.Resources>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListViewItem">
|
||||
<Grid x:Name="ItemGrid" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="350" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Grid.Column="0"/>
|
||||
<ContentPresenter Grid.Column="1" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="buttonGroup">
|
||||
<Button x:Name="EditListButton" Margin="10,0,5,0" Click="EditListButton_Click" AutomationProperties.Name="Edit Task List">
|
||||
<SymbolIcon Symbol="Edit"/>
|
||||
</Button>
|
||||
<Button x:Name="SaveListButton" Click="SaveListButton_Click" FlyoutBase.AttachedFlyout="{StaticResource SaveFlyout}" AutomationProperties.Name="Export Task List" ToolTipService.ToolTip="Export TaskList">
|
||||
<SymbolIcon Symbol="Save"/>
|
||||
</Button>
|
||||
<Button x:Name="DeleteListButton" Margin="5,0,5,0" Click="DeleteListButton_Click" AutomationProperties.Name="Delete task list">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListView.Resources>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="core:TaskListSummary">
|
||||
<TextBlock x:Name="TaskListName" Text="{x:Bind Name}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<StackPanel Grid.Row="10" Grid.Column="0" Orientation="Horizontal">
|
||||
<Button x:Name="SaveAllButton" Margin="5,0" Flyout="{StaticResource SaveFlyout}" AutomationProperties.LabeledBy="{Binding ElementName=SaveAll}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="SaveAll" Text="Save All TaskLists to File" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="DeleteAllButton" Click="DeleteAllButton_Click" AutomationProperties.LabeledBy="{Binding ElementName=DeleteAll}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
<TextBlock x:Name="DeleteAll" Text="Delete All TaskLists" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,411 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class SaveLoadEditPage : Page
|
||||
{
|
||||
public SaveLoadEditPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
_resetSem = new SemaphoreSlim(1, 1);
|
||||
TaskListCollection = new ObservableCollection<TaskListSummary>();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
mainPage = (Frame)e.Parameter;
|
||||
if (_taskListGuidPoller == null)
|
||||
{
|
||||
_taskListGuidPoller = new ServerPoller(null, typeof(TaskList), 2000);
|
||||
_taskListGuidPoller.OnUpdatedObject += OnUpdatedTaskListGuidsAsync;
|
||||
_taskListGuidPoller.OnException += ((App)Application.Current).OnServerPollerException;
|
||||
}
|
||||
|
||||
_taskListGuidPoller.StartPolling(Client);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
_taskListGuidPoller.StopPolling();
|
||||
}
|
||||
|
||||
private void NewListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
mainPage.Navigate(typeof(EditPage), null);
|
||||
this.OnNavigatedFrom(null);
|
||||
}
|
||||
|
||||
private void LoadFolderButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_isFileLoad = false;
|
||||
var button = sender as Button;
|
||||
LoadFlyoutTextHeader.Text = "Folder to load as TaskList:";
|
||||
Flyout.ShowAttachedFlyout(button);
|
||||
}
|
||||
|
||||
private void LoadFileButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_isFileLoad = true;
|
||||
LoadFlyoutTextHeader.Text = "TaskLists XML file to load from:";
|
||||
var button = sender as Button;
|
||||
Flyout.ShowAttachedFlyout(button);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel load flyout
|
||||
/// </summary>
|
||||
private void CancelLoad_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadFlyout.Hide();
|
||||
}
|
||||
|
||||
|
||||
private void ConfirmLoad_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == Windows.System.VirtualKey.Enter)
|
||||
{
|
||||
ConfirmLoad_Click(sender, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirm load flyout
|
||||
/// </summary>
|
||||
private async void ConfirmLoad_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(LoadFlyoutUserPath.Text))
|
||||
{
|
||||
var path = String.Copy(LoadFlyoutUserPath.Text);
|
||||
LoadProgressBar.Width = LoadFlyoutUserPath.ActualWidth - CancelLoad.ActualWidth - ConfirmLoad.ActualWidth - 30; // 30 == combined margin size
|
||||
LoadProgressBar.Visibility = Visibility.Visible;
|
||||
try
|
||||
{
|
||||
if (_isFileLoad)
|
||||
{
|
||||
var lists = await Client.LoadTaskListsFromXmlFile(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = await Client.CreateTaskListFromDirectory(path, true);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.GetType() != typeof(FactoryOrchestratorConnectionException))
|
||||
{
|
||||
ShowLoadFailure(_isFileLoad, path, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
LoadProgressBar.Visibility = Visibility.Collapsed;
|
||||
LoadFlyout.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private async void ShowLoadFailure(bool isFileLoad, string path, string error)
|
||||
{
|
||||
ContentDialog failedLoadDialog = new ContentDialog
|
||||
{
|
||||
Title = "Failed to load " + (isFileLoad ? "TaskLists XML file" : "folder"),
|
||||
Content = path + Environment.NewLine + Environment.NewLine,
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
failedLoadDialog.Content += error;
|
||||
}
|
||||
else
|
||||
{
|
||||
failedLoadDialog.Content += "Check the path and try again.";
|
||||
}
|
||||
|
||||
ContentDialogResult result = await failedLoadDialog.ShowAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the selected tasklist
|
||||
/// </summary>
|
||||
private async void DeleteListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var guid = GetTaskListGuidFromButton(sender as Button);
|
||||
await Client.DeleteTaskList(guid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the selected tasklist
|
||||
/// </summary>
|
||||
private void SaveListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var button = sender as Button;
|
||||
_activeGuid = GetTaskListGuidFromButton(button);
|
||||
SaveFlyoutUserPath.Text = _activeGuid.ToString();
|
||||
Flyout.ShowAttachedFlyout(button);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit the selected tasklist
|
||||
/// </summary>
|
||||
private async void EditListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var guid = GetTaskListGuidFromButton(sender as Button);
|
||||
try
|
||||
{
|
||||
var list = await Client.QueryTaskList(guid);
|
||||
mainPage.Navigate(typeof(EditPage), list);
|
||||
this.OnNavigatedFrom(null);
|
||||
}
|
||||
catch (FactoryOrchestratorUnkownGuidException ex)
|
||||
{
|
||||
ContentDialog failedQueryDialog = new ContentDialog
|
||||
{
|
||||
Title = "Failed to query TaskList for edit",
|
||||
Content = $"It may have been deleted." + Environment.NewLine + Environment.NewLine + ex.Message,
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await failedQueryDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel save flyout
|
||||
/// </summary>
|
||||
private void CancelSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveFlyout.Hide();
|
||||
}
|
||||
|
||||
private void ConfirmSave_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == Windows.System.VirtualKey.Enter)
|
||||
{
|
||||
ConfirmSave_Click(sender, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirm save flyout
|
||||
/// </summary>
|
||||
private async void ConfirmSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SaveFlyoutUserPath.Text))
|
||||
{
|
||||
var savePath = SaveFlyoutUserPath.Text + ".xml";
|
||||
SaveProgressBar.Width = SaveFlyoutUserPath.ActualWidth - CancelSave.ActualWidth - ConfirmSave.ActualWidth - 30; // 30 == combined margin size
|
||||
SaveProgressBar.Visibility = Visibility.Visible;
|
||||
|
||||
try
|
||||
{
|
||||
if (SaveAllButton.Flyout.IsOpen)
|
||||
{
|
||||
await Client.SaveAllTaskListsToXmlFile(savePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Client.SaveTaskListToXmlFile(_activeGuid, savePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.GetType() != typeof(FactoryOrchestratorConnectionException))
|
||||
{
|
||||
ContentDialog failedSaveDialog = new ContentDialog
|
||||
{
|
||||
Title = "Failed to save TaskLists XML file",
|
||||
Content = $"{ex.Message}",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await failedSaveDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
SaveProgressBar.Visibility = Visibility.Collapsed;
|
||||
SaveFlyout.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all known tasklists
|
||||
/// </summary>
|
||||
private async void DeleteAllButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ContentDialog clearAllDialog = new ContentDialog
|
||||
{
|
||||
Title = "Delete all TaskLists?",
|
||||
Content = "All running TaskLists will be stopped. All TaskLists will be removed from the server permanently.\n" +
|
||||
"Manually exported FactoryOrchestratorXML files will not be deleted, but will need to be manually imported via \"Load FactoryOrchestratorXML file\".\n\n" +
|
||||
"If \"Factory Reset\" is chosen, the service is restarted as if it is first boot. First boot and every boot tasks will re-run. Initial TaskLists will be loaded.\n" +
|
||||
"\"Factory Reset\" WILL interrupt communication with clients, including this app, until the boot tasks complete.",
|
||||
CloseButtonText = "Cancel",
|
||||
PrimaryButtonText = "Delete All",
|
||||
SecondaryButtonText = "Factory Reset"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await clearAllDialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
_resetSem.Wait();
|
||||
try
|
||||
{
|
||||
await Client.ResetService(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_resetSem.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (result == ContentDialogResult.Secondary)
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
_resetSem.Wait();
|
||||
try
|
||||
{
|
||||
await Client.ResetService(true, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_resetSem.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveFlyout_Closing(FlyoutBase sender, FlyoutBaseClosingEventArgs args)
|
||||
{
|
||||
SaveFlyoutUserPath.Text = "";
|
||||
}
|
||||
|
||||
private void LoadFlyout_Closing(FlyoutBase sender, FlyoutBaseClosingEventArgs args)
|
||||
{
|
||||
LoadFlyoutUserPath.Text = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the TaskLists are reordered by drag and drop. Sends updated order to the Service.
|
||||
/// </summary>
|
||||
private async void TaskListsView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!TaskListCollection.Any(x => x.IsRunningOrPending))
|
||||
{
|
||||
var newOrder = new List<Guid>();
|
||||
newOrder.AddRange(TaskListCollection.Select(x => x.Guid));
|
||||
await Client.ReorderTaskLists(newOrder);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FactoryOrchestratorException("Cannot reorder TaskLists while a TaskList is running!");
|
||||
}
|
||||
}
|
||||
catch (FactoryOrchestratorException ex)
|
||||
{
|
||||
// If it fails, the poller will update the UI to the old order.
|
||||
ContentDialog failedSaveDialog = new ContentDialog
|
||||
{
|
||||
Title = "Failed to reorder TaskLists",
|
||||
Content = $"{ex.Message}",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await failedSaveDialog.ShowAsync();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keeps the Known TaskLists in sync with the server.
|
||||
/// </summary>
|
||||
private async void OnUpdatedTaskListGuidsAsync(object source, ServerPollerEventArgs e)
|
||||
{
|
||||
var taskListSummaries = e.Result as List<TaskListSummary>;
|
||||
|
||||
if (taskListSummaries != null)
|
||||
{
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
// Add or update TaskLists
|
||||
for (int i = 0; i < taskListSummaries.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (i == TaskListCollection.Count)
|
||||
{
|
||||
TaskListCollection.Insert(i, taskListSummaries[i]);
|
||||
}
|
||||
else if (!TaskListCollection[i].Equals(taskListSummaries[i]))
|
||||
{
|
||||
TaskListCollection[i] = taskListSummaries[i];
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
Debug.WriteLine(ex.AllExceptionsToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Prune existing list
|
||||
int j = taskListSummaries.Count;
|
||||
while (TaskListCollection.Count > taskListSummaries.Count)
|
||||
{
|
||||
TaskListCollection.RemoveAt(j);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a button associated with a tasklist, returns the tasklist GUID.
|
||||
/// </summary>
|
||||
private Guid GetTaskListGuidFromButton(Button button)
|
||||
{
|
||||
var stack = button.Parent as StackPanel;
|
||||
var grid = stack.Parent as Grid;
|
||||
return ((TaskListSummary)((ContentPresenter)(grid.Children.Where(x => x.GetType() == typeof(ContentPresenter)).First())).Content).Guid;
|
||||
}
|
||||
|
||||
public ObservableCollection<TaskListSummary> TaskListCollection;
|
||||
private ServerPoller _taskListGuidPoller;
|
||||
private SemaphoreSlim _resetSem;
|
||||
private Guid _activeGuid;
|
||||
private bool _isFileLoad;
|
||||
private Frame mainPage;
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.TaskListExecutionPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:core="using:Microsoft.FactoryOrchestrator.Core"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Page.Resources>
|
||||
<local:TaskStatusDataBindingConverter x:Key="TaskStatusDataBindingConverter"/>
|
||||
<DataTemplate x:DataType="core:TaskListSummary" x:Key="TaskListItemTemplate_NotRun">
|
||||
<Grid x:Name="TaskListItemGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TaskListStatus" Text="{x:Bind Status, Converter={StaticResource TaskStatusDataBindingConverter}}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="buttonGroup">
|
||||
<Button x:Name="RunListButton" ToolTipService.ToolTip="Run" Margin="10,0,0,0" Click="RunListButton_Click" AutomationProperties.Name="Run task list">
|
||||
<SymbolIcon Symbol="Play"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:DataType="core:TaskListSummary" x:Key="TaskListItemTemplate_Running">
|
||||
<Grid x:Name="TaskListItemGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TaskListStatus" Text="{x:Bind Status, Converter={StaticResource TaskStatusDataBindingConverter}}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="buttonGroup">
|
||||
<Button x:Name="PauseListButton" ToolTipService.ToolTip="Pause" Margin="10,0,5,0" Click="PauseListButton_Click" AutomationProperties.Name="pause task list">
|
||||
<SymbolIcon Symbol="Pause"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:DataType="core:TaskListSummary" x:Key="TaskListItemTemplate_Paused">
|
||||
<Grid x:Name="TaskListItemGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TaskListStatus" Text="{x:Bind Status, Converter={StaticResource TaskStatusDataBindingConverter}}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="buttonGroup">
|
||||
<Button x:Name="ResumeListButton" ToolTipService.ToolTip="Resume" Margin="10,0,5,0" Click="ResumeListButton_Click" AutomationProperties.Name="resume task list">
|
||||
<SymbolIcon Symbol="Play"/>
|
||||
</Button>
|
||||
<Button x:Name="RestartListButton" ToolTipService.ToolTip="Restart" Click="RestartListButton_Click" AutomationProperties.Name="restart task list">
|
||||
<SymbolIcon Symbol="Refresh"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:DataType="core:TaskListSummary" x:Key="TaskListItemTemplate_Completed">
|
||||
<Grid x:Name="TaskListItemGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TaskListStatus" Text="{x:Bind Status, Converter={StaticResource TaskStatusDataBindingConverter}}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="buttonGroup">
|
||||
<Button x:Name="RestartListButton" ToolTipService.ToolTip="Rerun TaskList" Margin="10,0,0,0" Click="RestartListButton_Click" AutomationProperties.Name="re-run task list">
|
||||
<SymbolIcon Symbol="Play"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:DataType="local:TaskBaseWithTemplate" x:Key="RetryTaskBaseTemplate">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock x:Name="TaskStatus" Text="{x:Bind Task, Converter={StaticResource TaskStatusDataBindingConverter}, ConverterParameter=TaskBase}" VerticalAlignment="Center" Margin="10,0" Grid.Column="0" />
|
||||
<Button x:Name="RetryTaskButton" ToolTipService.ToolTip="Rerun Task" Click="RetryTaskButton_Click" Grid.Column="1">
|
||||
<SymbolIcon Symbol="Refresh" AutomationProperties.Name="re-run task" ToolTipService.ToolTip="Rerun Task"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:DataType="local:TaskBaseWithTemplate" x:Key="NormalTaskBaseTemplate">
|
||||
<TextBlock x:Name="TaskStatus" Text="{x:Bind Task, Converter={StaticResource TaskStatusDataBindingConverter}, ConverterParameter=TaskBase}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</DataTemplate>
|
||||
<local:TaskListViewSelector x:Key="TaskListViewSelector"
|
||||
Completed="{StaticResource TaskListItemTemplate_Completed}"
|
||||
Running="{StaticResource TaskListItemTemplate_Running}"
|
||||
NotRun="{StaticResource TaskListItemTemplate_NotRun}"
|
||||
Paused="{StaticResource TaskListItemTemplate_Paused}"/>
|
||||
<local:ResultsViewSelector x:Key="ResultsViewSelector"
|
||||
Normal="{StaticResource NormalTaskBaseTemplate}"
|
||||
RetryButtonShown="{StaticResource RetryTaskBaseTemplate}"/>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="0"/>
|
||||
<RowDefinition Height="0"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" >
|
||||
<TextBlock x:Name="TaskListsText" Text="Task Lists" Grid.Column="0" Grid.Row="0" Padding="10" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource TitleTextBlockStyle}"/>
|
||||
<Button x:Name="RunAllButton" Content="Run all" AutomationProperties.Name="Run all task lists" Click="RunAllButton_Click"/>
|
||||
<CheckBox x:Name="TrackExecutionCheck" Content="Track Execution" ToolTipService.ToolTip="Automatically update UI to follow the running Task(s)" IsChecked="True" IsThreeState="False" Checked="TrackExecutionCheck_Checked" Unchecked="TrackExecutionCheck_Checked" Margin="10, 0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" >
|
||||
<TextBlock x:Name="TasksText" Text="Tasks" Style="{StaticResource TitleTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Uses TaskListItemTemplate defined in page resources -->
|
||||
<ScrollViewer x:Name="TaskListsScrollView" HorizontalScrollBarVisibility="Disabled" Grid.Column="0" Grid.Row="1">
|
||||
<Grid x:Name="TaskListsScrollGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ListView x:Name="TaskListsView" Grid.Column="0" ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Disabled" ItemsSource="{x:Bind TaskListCollection, Mode=OneWay}" SelectionMode="Single" SelectionChanged="TaskListsView_SelectionChanged" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="core:TaskListSummary">
|
||||
<TextBlock x:Name="TaskListName" Text="{x:Bind Name}" ToolTipService.ToolTip="{x:Bind Name}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<ListView x:Name="TaskListsResultsAndButtonsView" Grid.Column="1" ScrollViewer.VerticalScrollMode="Disabled" ItemsSource="{x:Bind TaskListCollection, Mode=OneWay}" ItemTemplateSelector="{StaticResource TaskListViewSelector}" SelectionMode="Single" HorizontalAlignment="Right" SelectionChanged="TaskListsResultsAndButtonsView_SelectionChanged"/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<ScrollViewer x:Name="TestsScrollView" HorizontalScrollBarVisibility="Disabled" Grid.Column="1" Grid.Row="1" >
|
||||
<Grid x:Name="TestsScrollGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressRing x:Name="LoadingTasksRing" Grid.ColumnSpan="2" IsActive="False" Width="100" Height="100"/>
|
||||
<ListView x:Name="ActiveTestsView" Grid.Column="0" ScrollViewer.VerticalScrollMode="Disabled" ItemsSource="{x:Bind ActiveListCollection, Mode=OneWay}" SelectionMode="Single" ItemClick="ActiveTestsResultsView_ItemClick" IsItemClickEnabled="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:TaskBaseWithTemplate">
|
||||
<TextBlock x:Name="TaskName" Text="{x:Bind Task.Name}" ToolTipService.ToolTip="{x:Bind Task.Name}" VerticalAlignment="Center" Margin="10,0" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<ListView x:Name="ActiveTestsResultsView" Grid.Column="1" ScrollViewer.VerticalScrollMode="Disabled" ItemsSource="{x:Bind ActiveListCollection, Mode=OneWay}" ItemTemplateSelector="{StaticResource ResultsViewSelector}" SelectionMode="Single" ItemClick="ActiveTestsResultsView_ItemClick" IsItemClickEnabled="True" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<TextBlock x:Name="ResultsPreviewTaskName" Margin="0,5" Grid.Row="2" Grid.ColumnSpan="2" Visibility="Collapsed" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" Style="{StaticResource TitleTextBlockStyle}" IsTextSelectionEnabled="True"/>
|
||||
<ScrollViewer x:Name="ResultsPreviewScrollView" Margin="0,5" Grid.Row="3" Grid.ColumnSpan="2" Visibility="Collapsed" ViewChanged="ResultsPreviewScrollView_ViewChanged">
|
||||
<local:ResultsPage x:Name="ResultsPageEmbedded" SizeChanged="ResultsPageEmbedded_SizeChanged"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,629 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.FactoryOrchestrator.Client;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
using Windows.UI.Core;
|
||||
using System.Diagnostics;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class TaskListExecutionPage : Page
|
||||
{
|
||||
public TaskListExecutionPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
_selectedTaskList = -1;
|
||||
_selectedTaskListGuid = Guid.Empty;
|
||||
_selectedTaskGuid = Guid.Empty;
|
||||
_trackExecution = true;
|
||||
_headerUpdateLock = new object();
|
||||
mainPage = null;
|
||||
TaskListCollection = new ObservableCollection<TaskListSummary>();
|
||||
ActiveListCollection = new ObservableCollection<TaskBaseWithTemplate>();
|
||||
ResultsPageEmbedded.IsEmbedded = true;
|
||||
((App)Application.Current).OnServiceDoneExecutingBootTasks += TaskListExecutionPage_OnServiceDoneExecutingBootTasks;
|
||||
((App)Application.Current).OnServiceStart += TaskListExecutionPage_OnServiceStart;
|
||||
}
|
||||
|
||||
private void TaskListsView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (TaskListsView.SelectedItem != null)
|
||||
{
|
||||
var selectedTaskListGuid = ((TaskListSummary)TaskListsView.SelectedItem).Guid;
|
||||
|
||||
// Selection changed might have been due to updating a template, compare to _selectedTaskList
|
||||
if (_selectedTaskList != TaskListsView.SelectedIndex)
|
||||
{
|
||||
// New list selected, start over
|
||||
ActiveListCollection.Clear();
|
||||
_selectedTaskList = TaskListsView.SelectedIndex;
|
||||
_selectedTaskListGuid = TaskListCollection[_selectedTaskList].Guid;
|
||||
// Show loading ring
|
||||
LoadingTasksRing.IsActive = true;
|
||||
|
||||
if (_trackExecution)
|
||||
{
|
||||
EnsureSelectedIndexVisible(TaskListsView, TaskListsScrollView);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep indicies in sync
|
||||
TaskListsResultsAndButtonsView.SelectedIndex = _selectedTaskList;
|
||||
|
||||
// Create new poller
|
||||
if (_activeListPoller != null)
|
||||
{
|
||||
_activeListPoller.StopPolling();
|
||||
}
|
||||
_activeListPoller = new ServerPoller(selectedTaskListGuid, typeof(TaskList), 1000);
|
||||
_activeListPoller.OnUpdatedObject += OnUpdatedTaskListAsync;
|
||||
_activeListPoller.OnException += ((App)Application.Current).OnServerPollerException;
|
||||
_activeListPoller.StartPolling(Client);
|
||||
|
||||
// Show Tests
|
||||
ActiveTestsView.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop polling, hide tasks
|
||||
if (_activeListPoller != null)
|
||||
{
|
||||
_activeListPoller.StopPolling();
|
||||
_activeListPoller = null;
|
||||
}
|
||||
ActiveTestsView.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void TaskListsResultsAndButtonsView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if ((TaskListsResultsAndButtonsView.SelectedIndex != -1) && (_selectedTaskList != TaskListsResultsAndButtonsView.SelectedIndex))
|
||||
{
|
||||
// Select the tasklist to trigger TaskListsView_SelectionChanged
|
||||
TaskListsView.SelectedIndex = TaskListsResultsAndButtonsView.SelectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ActiveTestsResultsView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem != null)
|
||||
{
|
||||
var taskWithTemplate = (TaskBaseWithTemplate)(e.ClickedItem);
|
||||
// Navigate from the MainPage frame so this is a "full screen" page
|
||||
mainPage.Navigate(typeof(ResultsPage), taskWithTemplate.Task);
|
||||
this.OnNavigatedFrom(null);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnUpdatedTaskListAsync(object source, ServerPollerEventArgs e)
|
||||
{
|
||||
if (e.Result != null)
|
||||
{
|
||||
TaskList list = (TaskList)e.Result;
|
||||
var taskArray = list.Tasks.ToArray();
|
||||
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadingTasksRing.IsActive = false;
|
||||
|
||||
if (taskArray.Length == 0)
|
||||
{
|
||||
// No Tasks exist, clear everything
|
||||
ActiveListCollection.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < taskArray.Length; i++)
|
||||
{
|
||||
// Determine the template to use. Do here since it depends on the status of the list, not only the Task
|
||||
var newTask = taskArray[i];
|
||||
var newTemplate = (((list.TaskListStatus != TaskStatus.Running) &&
|
||||
(list.TaskListStatus != TaskStatus.RunPending)) &&
|
||||
(newTask.LatestTaskRunPassed != null) &&
|
||||
(newTask.LatestTaskRunPassed == false)) ? TaskViewTemplate.WithRetryButton : TaskViewTemplate.Normal;
|
||||
|
||||
// Update the ActiveListCollection
|
||||
try
|
||||
{
|
||||
if (i == ActiveListCollection.Count)
|
||||
{
|
||||
ActiveListCollection.Insert(i, new TaskBaseWithTemplate(newTask, newTemplate));
|
||||
}
|
||||
else if (!ActiveListCollection[i].Task.Equals(newTask))
|
||||
{
|
||||
// force template reselection
|
||||
ActiveListCollection[i] = new TaskBaseWithTemplate(newTask, newTemplate);
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
Debug.WriteLine(ex.AllExceptionsToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (_trackExecution)
|
||||
{
|
||||
// Show mini window with latest output
|
||||
var latestTask = taskArray.Where(x => x.LatestTaskRunStatus == TaskStatus.Running).DefaultIfEmpty(null).LastOrDefault();
|
||||
if (latestTask == null)
|
||||
{
|
||||
latestTask = taskArray.Where(x => x.LatestTaskRunStatus == TaskStatus.RunPending).DefaultIfEmpty(null).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (latestTask != null)
|
||||
{
|
||||
// Select the running task
|
||||
var item = ActiveListCollection.Where(x => x.Task.Guid == latestTask.Guid).First();
|
||||
ActiveTestsResultsView.SelectedItem = item;
|
||||
ActiveTestsView.SelectedItem = item;
|
||||
FollowOutput = true;
|
||||
|
||||
// Ensure the running task has changed before updating UI
|
||||
if (_selectedTaskGuid != latestTask.Guid)
|
||||
{
|
||||
// Prepare result preview
|
||||
_selectedTaskGuid = latestTask.Guid;
|
||||
await ResultsPageEmbedded.SetupForTask(latestTask);
|
||||
// Make result preview visible
|
||||
ResultsPreviewScrollView.Visibility = Visibility.Visible;
|
||||
ResultsPreviewTaskName.Visibility = Visibility.Visible;
|
||||
ResultsPreviewTaskName.Text = latestTask.Name;
|
||||
LayoutRoot.RowDefinitions.Last().Height = new GridLength(1, GridUnitType.Star);
|
||||
LayoutRoot.RowDefinitions[2].Height = GridLength.Auto;
|
||||
EnsureSelectedIndexVisible(ActiveTestsView, TestsScrollView);
|
||||
}
|
||||
}
|
||||
else if (!TaskListCollection.Any(x => (x.Guid != _selectedTaskListGuid) && (x.IsRunningOrPending)))
|
||||
{
|
||||
// No more tasks are queued to run. Hide preview.
|
||||
_selectedTaskGuid = Guid.Empty;
|
||||
EndTrackExecution();
|
||||
}
|
||||
}
|
||||
|
||||
// Prune non-existent Tasks
|
||||
int j = taskArray.Length;
|
||||
while (ActiveListCollection.Count > taskArray.Length)
|
||||
{
|
||||
ActiveListCollection.RemoveAt(j);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.AllExceptionsToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// No Tasks exist, clear everything
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
LoadingTasksRing.IsActive = false;
|
||||
ActiveListCollection.Clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnUpdatedTaskListGuidAndStatusAsync(object source, ServerPollerEventArgs e)
|
||||
{
|
||||
var newSummaries = (List<TaskListSummary>)e.Result;
|
||||
|
||||
// Get the new TaskLists
|
||||
if (newSummaries != null)
|
||||
{
|
||||
int newCount = newSummaries.Count;
|
||||
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (newSummaries.Any(x => x.IsRunningOrPending))
|
||||
{
|
||||
RunAllButton.Content = "Abort all";
|
||||
}
|
||||
else
|
||||
{
|
||||
RunAllButton.Content = "Run all";
|
||||
}
|
||||
|
||||
if (newCount == 0)
|
||||
{
|
||||
// No TaskLists exist, clear everything
|
||||
LoadingTasksRing.IsActive = false;
|
||||
TaskListCollection.Clear();
|
||||
ActiveListCollection.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_trackExecution)
|
||||
{
|
||||
// Find the latest running list. If none are running find the first run pending list.
|
||||
var latestList = newSummaries.Where(x => x.Status == TaskStatus.Running).DefaultIfEmpty(new TaskListSummary()).LastOrDefault();
|
||||
if (latestList.Guid == Guid.Empty)
|
||||
{
|
||||
latestList = newSummaries.Where(x => x.Status == TaskStatus.RunPending).DefaultIfEmpty(new TaskListSummary()).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (latestList.Guid != Guid.Empty)
|
||||
{
|
||||
_selectedTaskListGuid = latestList.Guid;
|
||||
}
|
||||
}
|
||||
|
||||
bool selectedListFound = false;
|
||||
for (int i = 0; i < newSummaries.Count; i++)
|
||||
{
|
||||
var newSummary = newSummaries[i];
|
||||
|
||||
if (newSummary.Guid == _selectedTaskListGuid)
|
||||
{
|
||||
selectedListFound = true;
|
||||
}
|
||||
|
||||
// Update the TaskListCollection
|
||||
if (i == TaskListCollection.Count)
|
||||
{
|
||||
TaskListCollection.Insert(i, newSummary);
|
||||
}
|
||||
else if (!TaskListCollection[i].Equals(newSummary))
|
||||
{
|
||||
// Template reselected automatically
|
||||
TaskListCollection[i] = newSummary;
|
||||
}
|
||||
|
||||
// Ensure correct list is selected
|
||||
if ((_selectedTaskListGuid == newSummary.Guid) && (TaskListsView.SelectedIndex != i))
|
||||
{
|
||||
TaskListsView.SelectedIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Prune non-existent Lists
|
||||
int j = newSummaries.Count;
|
||||
while (TaskListCollection.Count > newSummaries.Count)
|
||||
{
|
||||
TaskListCollection.RemoveAt(j);
|
||||
}
|
||||
|
||||
if (!selectedListFound && _selectedTaskList != -1)
|
||||
{
|
||||
// The selected list was deleted
|
||||
_selectedTaskList = -1;
|
||||
_selectedTaskListGuid = Guid.Empty;
|
||||
|
||||
TaskListsView.SelectedIndex = -1;
|
||||
ActiveListCollection.Clear();
|
||||
LoadingTasksRing.IsActive = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.AllExceptionsToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// No TaskLists exist, clear everything
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
LoadingTasksRing.IsActive = false;
|
||||
TaskListCollection.Clear();
|
||||
ActiveListCollection.Clear();
|
||||
EndTrackExecution();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
Client = ((App)Application.Current).Client;
|
||||
mainPage = (Frame)e.Parameter;
|
||||
EndTrackExecution();
|
||||
|
||||
if (((App)Application.Current).IsServiceExecutingBootTasks)
|
||||
{
|
||||
UpdateHeaders(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateHeaders(true);
|
||||
}
|
||||
|
||||
if (_activeListPoller != null)
|
||||
{
|
||||
_activeListPoller.StartPolling(Client);
|
||||
}
|
||||
|
||||
if (_taskListGuidPoller == null)
|
||||
{
|
||||
_taskListGuidPoller = new ServerPoller(null, typeof(TaskList), 1000, true, 2);
|
||||
_taskListGuidPoller.OnUpdatedObject += OnUpdatedTaskListGuidAndStatusAsync;
|
||||
_taskListGuidPoller.OnException += ((App)Application.Current).OnServerPollerException;
|
||||
}
|
||||
|
||||
_taskListGuidPoller.StartPolling(Client);
|
||||
|
||||
if (_selectedTaskList != -1)
|
||||
{
|
||||
TaskListsView.SelectedIndex = _selectedTaskList;
|
||||
if (_trackExecution)
|
||||
{
|
||||
EnsureSelectedIndexVisible(TaskListsView, TaskListsScrollView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for boot tasks completing.
|
||||
/// </summary>
|
||||
private async void TaskListExecutionPage_OnServiceDoneExecutingBootTasks()
|
||||
{
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
UpdateHeaders(true);
|
||||
});
|
||||
}
|
||||
|
||||
private async void TaskListExecutionPage_OnServiceStart()
|
||||
{
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
UpdateHeaders(false);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates headers depending on if boot tasks are executing.
|
||||
/// </summary>
|
||||
/// <param name="bootTasksComplete"></param>
|
||||
private void UpdateHeaders(bool bootTasksComplete)
|
||||
{
|
||||
lock (_headerUpdateLock)
|
||||
{
|
||||
if (bootTasksComplete)
|
||||
{
|
||||
TaskListsText.Text = "Task Lists";
|
||||
TasksText.Text = "Tasks";
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskListsText.Text = "Boot Task Lists";
|
||||
TasksText.Text = "Boot Tasks";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
if (_activeListPoller != null)
|
||||
{
|
||||
_activeListPoller.StopPolling();
|
||||
}
|
||||
|
||||
_taskListGuidPoller.StopPolling();
|
||||
}
|
||||
|
||||
private void RunAllButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (RunAllButton.Content.ToString().Contains("Run", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
_ = Client.RunAllTaskLists();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = Client.AbortAll();
|
||||
}
|
||||
}
|
||||
|
||||
// TaskList embedded buttons
|
||||
private void RunListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var guid = GetTaskListGuidFromButton(sender as Button);
|
||||
_ = Client.RunTaskList(guid);
|
||||
}
|
||||
|
||||
private async void ResumeListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var guid = GetTaskListGuidFromButton(sender as Button);
|
||||
var newList = await Client.QueryTaskList(guid);
|
||||
var lastTask = newList.Tasks.Where(x => x.LatestTaskRunStatus == TaskStatus.Aborted).DefaultIfEmpty(null).FirstOrDefault();
|
||||
var index = newList.Tasks.FindIndex(x => x.Guid.Equals(lastTask.Guid));
|
||||
_ = Client.RunTaskList(guid, index);
|
||||
}
|
||||
|
||||
private void PauseListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var guid = GetTaskListGuidFromButton(sender as Button);
|
||||
_ = Client.AbortTaskList(guid);
|
||||
}
|
||||
|
||||
private void RestartListButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RunListButton_Click(sender, e);
|
||||
}
|
||||
|
||||
private void RetryTaskButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var button = sender as Button;
|
||||
var guid = ((TaskBaseWithTemplate)button.DataContext).Task.Guid;
|
||||
_ = Client.RunTask(guid);
|
||||
}
|
||||
|
||||
private void TrackExecutionCheck_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_trackExecution = (bool)TrackExecutionCheck.IsChecked;
|
||||
if (!_trackExecution)
|
||||
{
|
||||
EndTrackExecution();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the selected index is visible to the user.
|
||||
/// </summary>
|
||||
/// <param name="list">ListView to check.</param>
|
||||
/// <param name="scroller">Scroll view the ListView is a child of.</param>
|
||||
private void EnsureSelectedIndexVisible(ListView list, ScrollViewer scroller)
|
||||
{
|
||||
// Get ListItem
|
||||
var element = list.ContainerFromIndex(list.SelectedIndex) as FrameworkElement;
|
||||
if (element != null)
|
||||
{
|
||||
// Calculate Y distance between list item and top of scroll view
|
||||
var transform = element.TransformToVisual(scroller);
|
||||
var pos = transform.TransformPoint(new Point(0, 0));
|
||||
// Add (or subtract) Y distance. Y can be negative.
|
||||
scroller.ChangeView(null, scroller.VerticalOffset + pos.Y, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops tracking execution, hides results preview.
|
||||
/// </summary>
|
||||
private void EndTrackExecution()
|
||||
{
|
||||
ResultsPreviewScrollView.Visibility = Visibility.Collapsed;
|
||||
ResultsPreviewTaskName.Visibility = Visibility.Collapsed;
|
||||
ResultsPageEmbedded.StopPolling();
|
||||
LayoutRoot.RowDefinitions.Last().Height = new GridLength(0);
|
||||
LayoutRoot.RowDefinitions[2].Height = new GridLength(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a button associated with a tasklist, returns the tasklist guid.
|
||||
/// </summary>
|
||||
private Guid GetTaskListGuidFromButton(Button button)
|
||||
{
|
||||
return ((TaskListSummary)button.DataContext).Guid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the user interacts with the scroll view.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ResultsPreviewScrollView_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
|
||||
{
|
||||
// Stop following output
|
||||
FollowOutput = false;
|
||||
|
||||
if (e.IsIntermediate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ResultsPreviewScrollView.ScrollableHeight == ResultsPreviewScrollView.VerticalOffset)
|
||||
{
|
||||
// User scrolled to end, resume following output
|
||||
FollowOutput = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporary handler for scroll event, only used for automated scroll events generated by ResultsPageEmbedded_SizeChanged.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Temporary_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
|
||||
{
|
||||
if (e.IsIntermediate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll is finished. Allow User to interact with scroll bar again.
|
||||
ResultsPreviewScrollView.ViewChanged -= Temporary_ViewChanged;
|
||||
ResultsPreviewScrollView.ViewChanged -= ResultsPreviewScrollView_ViewChanged;
|
||||
ResultsPreviewScrollView.ViewChanged += ResultsPreviewScrollView_ViewChanged;
|
||||
|
||||
ResultsPreviewScrollView.HorizontalScrollMode = ScrollMode.Enabled;
|
||||
ResultsPreviewScrollView.VerticalScrollMode = ScrollMode.Enabled;
|
||||
ResultsPreviewScrollView.ZoomMode = ZoomMode.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when results preview has new output. Scrolls to end of output if FollowOutput is enabled.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ResultsPageEmbedded_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (FollowOutput)
|
||||
{
|
||||
// Prevent user from interacting with scroll bar.
|
||||
ResultsPreviewScrollView.HorizontalScrollMode = ScrollMode.Disabled;
|
||||
ResultsPreviewScrollView.VerticalScrollMode = ScrollMode.Disabled;
|
||||
ResultsPreviewScrollView.ZoomMode = ZoomMode.Disabled;
|
||||
|
||||
ResultsPreviewScrollView.ViewChanged -= Temporary_ViewChanged;
|
||||
ResultsPreviewScrollView.ViewChanged -= ResultsPreviewScrollView_ViewChanged;
|
||||
ResultsPreviewScrollView.ViewChanged += Temporary_ViewChanged;
|
||||
|
||||
// Auto scroll to end of output
|
||||
ResultsPreviewScrollView.ChangeView(null, ResultsPreviewScrollView.ScrollableHeight, null);
|
||||
}
|
||||
}
|
||||
|
||||
private Frame mainPage;
|
||||
private ServerPoller _activeListPoller;
|
||||
private ServerPoller _taskListGuidPoller;
|
||||
private int _selectedTaskList;
|
||||
private Guid _selectedTaskListGuid;
|
||||
private Guid _selectedTaskGuid;
|
||||
private bool _trackExecution;
|
||||
private object _headerUpdateLock;
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
public ObservableCollection<TaskListSummary> TaskListCollection;
|
||||
public ObservableCollection<TaskBaseWithTemplate> ActiveListCollection;
|
||||
public bool FollowOutput { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum for the possible Task DataTemplates.
|
||||
/// </summary>
|
||||
public enum TaskViewTemplate
|
||||
{
|
||||
Normal,
|
||||
WithRetryButton
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A basic wrapper struct to associate a Task with the DataTemplate it should use in the UI.
|
||||
/// This is needed since the UI is dependent on the TaskList state, not just the Task.
|
||||
/// </summary>
|
||||
public struct TaskBaseWithTemplate
|
||||
{
|
||||
public TaskBaseWithTemplate(TaskBase task, TaskViewTemplate template)
|
||||
{
|
||||
Task = task;
|
||||
Template = template;
|
||||
}
|
||||
|
||||
public TaskBase Task { get; set; }
|
||||
|
||||
public TaskViewTemplate Template { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// A UI helper class to select the correct DataTemplate to use for TaskListsView items.
|
||||
/// </summary>
|
||||
public class TaskListViewSelector : DataTemplateSelector
|
||||
{
|
||||
// These templates are set by TaskListExecutionPage.xaml which instantiates the TaskListViewSelector.
|
||||
public DataTemplate Running { get; set; }
|
||||
public DataTemplate Paused { get; set; }
|
||||
public DataTemplate Completed { get; set; }
|
||||
public DataTemplate NotRun { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the template to use for a given TaskListSummaryWithTemplate.
|
||||
/// Called every time a list item in TaskListsView changes.
|
||||
/// </summary>
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
FrameworkElement element = container as FrameworkElement;
|
||||
DataTemplate dataTemplate = null;
|
||||
try
|
||||
{
|
||||
if (element != null && item != null && item is TaskListSummary)
|
||||
{
|
||||
var list = (TaskListSummary)item;
|
||||
switch (list.Status)
|
||||
{
|
||||
case TaskStatus.Running:
|
||||
case TaskStatus.RunPending:
|
||||
dataTemplate = Running;
|
||||
break;
|
||||
case TaskStatus.Aborted:
|
||||
if (list.RunInParallel)
|
||||
{
|
||||
dataTemplate = Completed;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataTemplate = Paused;
|
||||
}
|
||||
break;
|
||||
case TaskStatus.Passed:
|
||||
case TaskStatus.Failed:
|
||||
dataTemplate = Completed;
|
||||
break;
|
||||
default:
|
||||
dataTemplate = NotRun;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e.AllExceptionsToString());
|
||||
dataTemplate = NotRun;
|
||||
}
|
||||
|
||||
return dataTemplate;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
class TaskStatusDataBindingConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var paramStr = parameter as String;
|
||||
|
||||
if (paramStr == null)
|
||||
{
|
||||
return ConvertStatus(value, true);
|
||||
}
|
||||
if (paramStr.Equals("TaskBase", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return ConvertStatus(value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("TaskBaseQuickStatusConverter parameter is unrecognized");
|
||||
}
|
||||
}
|
||||
|
||||
private string ConvertStatus(object value, bool isStatus)
|
||||
{
|
||||
String status = "";
|
||||
TaskStatus statusEnum;
|
||||
// value is the data from the source object.
|
||||
TaskBase task = value as TaskBase;
|
||||
|
||||
if (isStatus)
|
||||
{
|
||||
statusEnum = (TaskStatus)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
statusEnum = task.LatestTaskRunStatus;
|
||||
}
|
||||
|
||||
switch (statusEnum)
|
||||
{
|
||||
case TaskStatus.Passed:
|
||||
status += "✔ Passed";
|
||||
if ((!isStatus) && (task.TimesRetried > 0))
|
||||
{
|
||||
status += $" (On retry {task.TimesRetried})";
|
||||
}
|
||||
if ((!isStatus) && (task.TaskRunGuids.Count > 1) && (task.TaskRunGuids.Count > task.TimesRetried))
|
||||
{
|
||||
status += $" ({task.TaskRunGuids.Count} total runs)";
|
||||
}
|
||||
break;
|
||||
case TaskStatus.Failed:
|
||||
status += "❌ Failed";
|
||||
if ((!isStatus) && (task.TimesRetried > 0))
|
||||
{
|
||||
status += $" (All {task.TimesRetried} retries)";
|
||||
}
|
||||
if ((!isStatus) && (task.TaskRunGuids.Count > 1) && (task.TaskRunGuids.Count > task.TimesRetried))
|
||||
{
|
||||
status += $" ({task.TaskRunGuids.Count} total runs)";
|
||||
}
|
||||
break;
|
||||
case TaskStatus.Running:
|
||||
status += "▶ Running";
|
||||
if ((!isStatus) && (task.TimesRetried > 0))
|
||||
{
|
||||
status += $" (Retry {task.TimesRetried})";
|
||||
}
|
||||
break;
|
||||
case TaskStatus.NotRun:
|
||||
status += "❔ Not Run";
|
||||
break;
|
||||
case TaskStatus.Aborted:
|
||||
status += "⛔ Aborted";
|
||||
if ((!isStatus) && (task.TaskRunGuids.Count > 1) && (task.TaskRunGuids.Count > task.TimesRetried))
|
||||
{
|
||||
status += $" ({task.TaskRunGuids.Count} total runs)";
|
||||
}
|
||||
break;
|
||||
case TaskStatus.Timeout:
|
||||
status += "⏱ Timed-out";
|
||||
if ((!isStatus) && (task.TaskRunGuids.Count > 1) && (task.TaskRunGuids.Count > task.TimesRetried))
|
||||
{
|
||||
status += $" ({task.TaskRunGuids.Count} total runs)";
|
||||
}
|
||||
break;
|
||||
case TaskStatus.RunPending:
|
||||
status += "❔ Run Pending";
|
||||
break;
|
||||
default:
|
||||
status += "❔ Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// ConvertBack is not implemented for a OneWay binding.
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<Page
|
||||
x:Class="Microsoft.FactoryOrchestrator.UWP.WdpPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.FactoryOrchestrator.UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<WebView x:Name="wdp"/>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class WdpPage : Page
|
||||
{
|
||||
public WdpPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
}
|
||||
|
||||
private async Task<bool> IsWindowsDevicePortalRunning()
|
||||
{
|
||||
List<string> InstalledApps = new List<string>();
|
||||
try
|
||||
{
|
||||
InstalledApps = await Client.GetInstalledApps();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
|
||||
if (await IsWindowsDevicePortalRunning())
|
||||
{
|
||||
string ipAddress = Client.IsLocalHost ? "localhost" : $"{Client.IpAddress.ToString()}";
|
||||
string url = "http://" + ipAddress + ":80";
|
||||
Uri myUri = new Uri(url);
|
||||
wdp.Navigate(myUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentDialog failedAppsDialog = new ContentDialog
|
||||
{
|
||||
Title = "Failed to launch Windows Device Portal",
|
||||
Content = "Make sure Windows Device Portal is running and try again.",
|
||||
CloseButtonText = "Ok"
|
||||
};
|
||||
|
||||
ContentDialogResult result = await failedAppsDialog.ShowAsync();
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
}
|
||||
|
||||
private FactoryOrchestratorUWPClient Client = ((App)Application.Current).Client;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
###############
|
||||
# folder #
|
||||
###############
|
||||
/**/DROP/
|
||||
/**/TEMP/
|
||||
/**/packages/
|
||||
/**/bin/
|
||||
/**/obj/
|
||||
_site
|
||||
log.txt
|
|
@ -0,0 +1,544 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using JKang.IpcServiceFramework;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class for Factory Orchestrator .NET clients. It wraps the inter-process calls in a more usable manner.
|
||||
/// WARNING: Use FactoryOrchestratorUWPClient for UWP clients or your UWP app will crash!
|
||||
/// </summary>
|
||||
public partial class FactoryOrchestratorClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new FactoryOrchestratorClient instance. WARNING: Use FactoryOrchestratorUWPClient for UWP clients or your UWP app will crash!
|
||||
/// </summary>
|
||||
/// <param name="host">IP address of the device running Factory Orchestrator Service. Use IPAddress.Loopback for local device.</param>
|
||||
/// <param name="port">Port to use. Factory Orchestrator Service defaults to 45684.</param>
|
||||
public FactoryOrchestratorClient(IPAddress host, int port = 45684)
|
||||
{
|
||||
OnConnected = null;
|
||||
_IpcClient = null;
|
||||
IsConnected = false;
|
||||
IpAddress = host;
|
||||
Port = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Establishes a connection to the Factory Orchestrator Service.
|
||||
/// Throws an exception if it cannot connect.
|
||||
/// </summary>
|
||||
/// <param name="ignoreVersionMismatch">If true, ignore a Client-Service version mismatch.</param>
|
||||
public async Task Connect(bool ignoreVersionMismatch = false)
|
||||
{
|
||||
_IpcClient = new IpcServiceClientBuilder<IFactoryOrchestratorService>()
|
||||
.UseTcp(IpAddress, Port)
|
||||
.Build();
|
||||
|
||||
string serviceVersion;
|
||||
// Test a command to make sure connection works
|
||||
try
|
||||
{
|
||||
serviceVersion = await _IpcClient.InvokeAsync<string>(CreateIpcRequest("GetServiceVersionString"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
|
||||
if (!ignoreVersionMismatch)
|
||||
{
|
||||
// Verify client and service are compatible
|
||||
var clientVersion = GetClientVersionString();
|
||||
var clientMajor = clientVersion.Split('.').First();
|
||||
var serviceMajor = serviceVersion.Split('.').First();
|
||||
|
||||
if (clientMajor != serviceMajor)
|
||||
{
|
||||
throw new FactoryOrchestratorVersionMismatchException(IpAddress, serviceVersion, clientVersion);
|
||||
}
|
||||
}
|
||||
|
||||
IsConnected = true;
|
||||
OnConnected?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to establish a connection to the Factory Orchestrator Service.
|
||||
/// </summary>
|
||||
/// <param name="ignoreVersionMismatch">If true, ignore a Client-Service version mismatch.</param>
|
||||
/// <returns>true if it was able to connect.</returns>
|
||||
public async Task<bool> TryConnect(bool ignoreVersionMismatch = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Connect(ignoreVersionMismatch);
|
||||
return true;
|
||||
}
|
||||
catch (FactoryOrchestratorConnectionException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies an app package to the Service and installs it. Requires Windows Device Portal.
|
||||
/// If the app package is already on the Service's computer, use InstallApp() instead.
|
||||
/// </summary>
|
||||
/// <param name="appFilename">Path on the Client's computer to the app package (.appx, .appxbundle, .msix, .msixbundle).</param>
|
||||
/// <param name="dependentPackages">List of paths on the Client's computer to the app's dependent packages.</param>
|
||||
/// <param name="certificateFile">Path on the Client's computer to the app's certificate file, if needed. Microsoft Store signed apps do not need a certificate.</param>
|
||||
public async Task SendAndInstallApp(string appFilename, List<string> dependentPackages = null, string certificateFile = null)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await WDPHelpers.InstallAppWithWDP(appFilename, dependentPackages, certificateFile, IpAddress.ToString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a file from the client to the device running Factory Orchestrator Service. Creates directories if needed.
|
||||
/// </summary>
|
||||
/// <param name="clientFilename">Path on client PC to the file to copy.</param>
|
||||
/// <param name="serverFilename">Path on device running Factory Orchestrator Service where the file will be saved.</param>
|
||||
public async Task<long> SendFileToDevice(string clientFilename, string serverFilename)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
clientFilename = Environment.ExpandEnvironmentVariables(clientFilename);
|
||||
|
||||
if (!File.Exists(clientFilename))
|
||||
{
|
||||
throw new FileNotFoundException($"{clientFilename} does not exist!");
|
||||
}
|
||||
|
||||
var bytes = await ReadFileAsync(clientFilename);
|
||||
|
||||
await _IpcClient.InvokeAsync(CreateIpcRequest("SendFile", serverFilename, bytes));
|
||||
return bytes.Length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a file from the device running Factory Orchestrator Service to the client. Creates directories if needed.
|
||||
/// </summary>
|
||||
/// <param name="serverFilename">Path on device running Factory Orchestrator Service to the file to copy.</param>
|
||||
/// <param name="clientFilename">Path on client PC where the file will be saved.</param>
|
||||
public async Task<long> GetFileFromDevice(string serverFilename, string clientFilename)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
clientFilename = Environment.ExpandEnvironmentVariables(clientFilename);
|
||||
|
||||
// Create target folder, if needed.
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(clientFilename));
|
||||
|
||||
var bytes = await _IpcClient.InvokeAsync<byte[]>(CreateIpcRequest("GetFile", serverFilename));
|
||||
await WriteFileAsync(clientFilename, bytes);
|
||||
return bytes.Length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a folder from the device running Factory Orchestrator Service to the client. Creates directories if needed.
|
||||
/// </summary>
|
||||
/// <param name="serverDirectory">Path on device running Factory Orchestrator Service to the folder to copy.</param>
|
||||
/// <param name="clientDirectory">Path on client PC where the folder will be saved.</param>
|
||||
public async Task<long> GetDirectoryFromDevice(string serverDirectory, string clientDirectory)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var files = await _IpcClient.InvokeAsync<List<string>>(CreateIpcRequest("EnumerateFiles", serverDirectory, false));
|
||||
var dirs = await _IpcClient.InvokeAsync<List<string>>(CreateIpcRequest("EnumerateDirectories", serverDirectory, false));
|
||||
long bytesReceived = 0;
|
||||
|
||||
clientDirectory = Environment.ExpandEnvironmentVariables(clientDirectory);
|
||||
if (!Directory.Exists(clientDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(clientDirectory);
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var filename = Path.GetFileName(file);
|
||||
bytesReceived += await GetFileFromDevice(file, Path.Combine(clientDirectory, filename));
|
||||
}
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
var subDirName = new DirectoryInfo(dir).Name;
|
||||
var clientsubDir = Path.Combine(clientDirectory, subDirName);
|
||||
|
||||
if (!Directory.Exists(clientsubDir))
|
||||
{
|
||||
Directory.CreateDirectory(clientsubDir);
|
||||
}
|
||||
|
||||
bytesReceived += await GetDirectoryFromDevice(dir, clientsubDir);
|
||||
}
|
||||
|
||||
return bytesReceived;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a folder from the client to the device running Factory Orchestrator Service. Creates directories if needed.
|
||||
/// </summary>
|
||||
/// <param name="clientDirectory">Path on client PC to the folder to copy.</param>
|
||||
/// <param name="serverDirectory">Path on device running Factory Orchestrator Service where the folder will be saved.</param>
|
||||
public async Task<long> SendDirectoryToDevice(string clientDirectory, string serverDirectory)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
clientDirectory = Environment.ExpandEnvironmentVariables(clientDirectory);
|
||||
if (!Directory.Exists(clientDirectory))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"{clientDirectory} does not exist!");
|
||||
}
|
||||
|
||||
var files = Directory.EnumerateFiles(clientDirectory);
|
||||
var dirs = Directory.EnumerateDirectories(clientDirectory);
|
||||
long bytesSent = 0;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
bytesSent += await SendFileToDevice(file, Path.Combine(serverDirectory, Path.GetFileName(file)));
|
||||
}
|
||||
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
bytesSent += await SendDirectoryToDevice(dir, Path.Combine(serverDirectory, new DirectoryInfo(dir).Name));
|
||||
}
|
||||
|
||||
return bytesSent;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown the device running Factory Orchestrator Service.
|
||||
/// </summary>
|
||||
/// <param name="secondsUntilShutdown">How long to delay shutdown, in seconds.</param>
|
||||
public async void ShutdownDevice(uint secondsUntilShutdown = 0)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RunExecutable(@"%systemroot%\system32\shutdown.exe", $"/s /t {secondsUntilShutdown}", null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reboot the device running Factory Orchestrator Service.
|
||||
/// </summary>
|
||||
/// <param name="secondsUntilReboot">How long to delay reboot, in seconds.</param>
|
||||
public async void RebootDevice(uint secondsUntilReboot = 0)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RunExecutable(@"%systemroot%\system32\shutdown.exe", $"/r /t {secondsUntilReboot}", null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw CreateIpcException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the build number of FactoryOrchestratorClient.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetClientVersionString()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyVersion = assembly.GetName().Version.ToString();
|
||||
object[] attributes = assembly.GetCustomAttributes(true);
|
||||
|
||||
string description = "";
|
||||
|
||||
var descrAttr = attributes.OfType<AssemblyDescriptionAttribute>().FirstOrDefault();
|
||||
if (descrAttr != null)
|
||||
{
|
||||
description = descrAttr.Description;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
description = "Debug" + description;
|
||||
#endif
|
||||
|
||||
return $"{assemblyVersion} ({description})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the IPC request.
|
||||
/// </summary>
|
||||
/// <param name="methodName">Name of the method.</param>
|
||||
/// <param name="args">The arguments to the method.</param>
|
||||
/// <returns>IpcRequest object</returns>
|
||||
private IpcRequest CreateIpcRequest(string methodName, params object[] args)
|
||||
{
|
||||
|
||||
MethodBase method = null;
|
||||
|
||||
// Try to find the matching method based on name and args
|
||||
try
|
||||
{
|
||||
if (args.All(x => x != null))
|
||||
{
|
||||
method = this.GetType().GetMethod(methodName, args.Select(x => x.GetType()).ToArray());
|
||||
}
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
method = this.GetType().GetMethod(methodName);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
method = null;
|
||||
}
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
// Multiple methods with the same name were found or no method was found, try to find the unique method via stack trace
|
||||
var frame = new StackTrace().GetFrames().Where(x => x.GetMethod()?.Name == methodName);
|
||||
|
||||
if (frame.Count() == 0)
|
||||
{
|
||||
throw new Exception($"Could not find method with name {methodName}");
|
||||
}
|
||||
if (frame.Count() > 1)
|
||||
{
|
||||
throw new Exception($"More than one method with name {methodName}");
|
||||
}
|
||||
|
||||
method = frame.First().GetMethod();
|
||||
}
|
||||
|
||||
var parameterTypes = method.GetParameters().Select(x => x.ParameterType);
|
||||
|
||||
var request = new IpcRequest()
|
||||
{
|
||||
MethodName = methodName,
|
||||
Parameters = args,
|
||||
ParameterAssemblyNames = parameterTypes.Select(x => x.Assembly.GetName().Name).ToArray(),
|
||||
ParameterTypes = parameterTypes.Select(x => x.FullName).ToArray(),
|
||||
GenericArguments = method.GetGenericArguments()
|
||||
};
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates the IPC request.
|
||||
/// </summary>
|
||||
/// <param name="methodName">Name of the method. The method is assumed to have no parameters.</param>
|
||||
/// <returns>IpcRequest object</returns>
|
||||
private IpcRequest CreateIpcRequest(string methodName)
|
||||
{
|
||||
return new IpcRequest()
|
||||
{
|
||||
MethodName = methodName,
|
||||
Parameters = new object[0],
|
||||
ParameterAssemblyNames = new string[0],
|
||||
ParameterTypes = new string[0]
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a FactoryOrchestratorConnectionException if needed.
|
||||
/// </summary>
|
||||
private Exception CreateIpcException(Exception ex)
|
||||
{
|
||||
if (ex.HResult == -2147467259 || (ex.GetType() == typeof(ArgumentOutOfRangeException) && ex.Message.Contains("Header length must be 4 but was ")) || (ex.InnerException != null && ex.InnerException.GetType() == typeof(System.Net.Sockets.SocketException)))
|
||||
{
|
||||
IsConnected = false;
|
||||
ex = new FactoryOrchestratorConnectionException(IpAddress);
|
||||
}
|
||||
|
||||
return ex;
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <summary>
|
||||
/// Writes bytes to file.
|
||||
/// </summary>
|
||||
/// <param name="file">File to write.</param>
|
||||
/// <param name="data">Bytes to write to file.</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task WriteFileAsync(string file, byte[] data)
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
{
|
||||
File.WriteAllBytes(file, data);
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <summary>
|
||||
/// Read bytes from file.
|
||||
/// </summary>
|
||||
/// <param name="file">File to read.</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<byte[]> ReadFileAsync(string file)
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
{
|
||||
return File.ReadAllBytes(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when Client-Service connection is successfully established.
|
||||
/// </summary>
|
||||
public event IPCClientOnConnected OnConnected;
|
||||
|
||||
/// <summary>
|
||||
/// True if Factory Orchestrator Service is running on the local device.
|
||||
/// </summary>
|
||||
public bool IsLocalHost
|
||||
{
|
||||
get
|
||||
{
|
||||
return (IpAddress == IPAddress.Loopback) ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the Client-Service connection is successfully established.
|
||||
/// </summary>
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IP address of the connected device.
|
||||
/// </summary>
|
||||
public IPAddress IpAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The port of the connected device used. Factory Orchestrator Service defaults to 45684.
|
||||
/// </summary>
|
||||
public int Port { get; private set; }
|
||||
|
||||
private IpcServiceClient<IFactoryOrchestratorService> _IpcClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature for event handlers.
|
||||
/// </summary>
|
||||
public delegate void IPCClientOnConnected();
|
||||
|
||||
/// <summary>
|
||||
/// A FactoryOrchestratorConnectionException describes a Factory Orchestrator Client-Service connection issue.
|
||||
/// </summary>
|
||||
public class FactoryOrchestratorConnectionException : FactoryOrchestratorException
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="ip">IP address Client is communicating with.</param>
|
||||
public FactoryOrchestratorConnectionException(IPAddress ip) : base($"Failed to communicate with Factory Orchestrator Service on {ip}!")
|
||||
{ }
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="message">Exception text.</param>
|
||||
public FactoryOrchestratorConnectionException(string message) : base(message)
|
||||
{ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A FactoryOrchestratorVersionMismatchException is thrown if the Major versions of the Client and Service are incompatable.
|
||||
/// </summary>
|
||||
public class FactoryOrchestratorVersionMismatchException : FactoryOrchestratorException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FactoryOrchestratorVersionMismatchException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ip">The ip address of the Service.</param>
|
||||
/// <param name="serviceVersion">The service version.</param>
|
||||
/// <param name="clientVersion">The client version.</param>
|
||||
public FactoryOrchestratorVersionMismatchException(IPAddress ip, string serviceVersion, string clientVersion) : base($"Factory Orchestrator Service on {ip} has version {serviceVersion} which is incompatable with FactoryOrchestratorClient version {clientVersion}! Use Connect(true) or TryConnect(true) to ignore this error when connecting.")
|
||||
{
|
||||
ServiceVersion = serviceVersion;
|
||||
ClientVersion = clientVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client version.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The client version.
|
||||
/// </value>
|
||||
public string ClientVersion { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the service version.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The service version.
|
||||
/// </value>
|
||||
public string ServiceVersion { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetName>FactoryOrchestratorClientLibrary</TargetName>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(XES_OUTDIR)' == ''">
|
||||
<OutputPath>..\bin\$(Configuration)\$(Platform)\$(TargetName)</OutputPath>
|
||||
<DocumentationFile>..\bin\$(Configuration)\$(Platform)\$(TargetName)\$(TargetName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(XES_OUTDIR)' != ''">
|
||||
<OutputPath>$(BUILD_ARTIFACTSTAGINGDIRECTORY)\bin\$(Configuration)\$(Platform)\$(TargetName)</OutputPath>
|
||||
<DocumentationFile>$(BUILD_ARTIFACTSTAGINGDIRECTORY)\bin\$(Configuration)\$(Platform)\$(TargetName)\$(TargetName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(XES_OUTDIR)' != ''">
|
||||
<PackageReference Include="docfx.console" Version="2.47.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="obj\AssemblyInfo.cs" />
|
||||
<Compile Remove="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CoreLibrary\FactoryOrchestratorCoreLibrary.csproj" />
|
||||
<ProjectReference Include="..\IpcFramework\IpcServiceFramework.Client\IpcServiceFramework.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="BeforeBuildPS_VSO" BeforeTargets="Build;CoreCompile" Condition="'$(IsTFSBuild)' == 'true'">
|
||||
<Exec Command="Powershell.exe $(ProjectDir)..\eng\build\SetSourceVersion.ps1 -SrcPath $(ProjectDir)" />
|
||||
</Target>
|
||||
<Target Name="BeforeBuildPS_Local" BeforeTargets="Build;CoreCompile" Condition="'$(IsTFSBuild)' != 'true'">
|
||||
<Exec Command="Powershell.exe $(ProjectDir)..\eng\build\SetSourceVersion.ps1 -SrcPath $(ProjectDir) -MajorMinorOnly" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using JKang.IpcServiceFramework;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Microsoft.FactoryOrchestrator.Core;
|
||||
using TaskStatus = Microsoft.FactoryOrchestrator.Core.TaskStatus;
|
||||
|
||||
namespace Microsoft.FactoryOrchestrator.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory Ochestrator uses a polling model. ServerPoller is used to create a polling thread for a given Factory Ochestrator GUID. It can optionally raise a ServerPollerEvent event via OnUpdatedObject.
|
||||
/// All Factory Orchestrator GUID types are supported.
|
||||
/// </summary>
|
||||
public class ServerPoller
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new ServerPoller. The ServerPoller is associated with a specific FactoryOrchestratorClient and object you want to poll. The desired object is referred to by its GUID. The GUID can be NULL for TaskRun polling.
|
||||
/// If it is NULL and the guidType is TaskList, a List of TaskListSummary objects is returned.
|
||||
/// </summary>
|
||||
/// <param name="guidToPoll">GUID of the object you want to poll</param>
|
||||
/// <param name="guidType">The type of object that GUID is for</param>
|
||||
/// <param name="pollingIntervalMs">How frequently the polling should be done, in milliseconds. Defaults to 500ms.</param>
|
||||
/// <param name="adaptiveInterval">If true, automatically adjust the polling interval for best performance. Defaults to true.</param>
|
||||
/// <param name="maxAdaptiveModifier">If adaptiveInterval is set, this defines the maximum multiplier/divisor that will be applied to the polling interval. For example, if maxAdaptiveModifier=2 and pollingIntervalMs=100, the object would be polled at a rate between 50ms to 200ms. Defaults to 5.</param>
|
||||
public ServerPoller(Guid? guidToPoll, Type guidType, int pollingIntervalMs = 500, bool adaptiveInterval = true, int maxAdaptiveModifier = 3)
|
||||
{
|
||||
PollingGuid = guidToPoll;
|
||||
_pollingInterval = pollingIntervalMs;
|
||||
_initialPollingInterval = pollingIntervalMs;
|
||||
_pollingIntervalStep = pollingIntervalMs / 10;
|
||||
LatestObject = null;
|
||||
_lastEventObject = null;
|
||||
_adaptiveInterval = adaptiveInterval;
|
||||
_adaptiveModifier = maxAdaptiveModifier;
|
||||
_timer = new Timer(GetUpdatedObjectAsync, null, Timeout.Infinite, pollingIntervalMs);
|
||||
_invokeSem = new SemaphoreSlim(1, 1);
|
||||
_stopped = true;
|
||||
OnUpdatedObject = null;
|
||||
OnException = null;
|
||||
OnlyRaiseOnExceptionEventForConnectionException = false;
|
||||
|
||||
if ((guidType != typeof(TaskBase)) && (guidType != typeof(ExecutableTask)) && (guidType != typeof(UWPTask)) && (guidType != typeof(TAEFTest)) && (guidType != typeof(TaskList)) && (guidType != typeof(TaskRun)))
|
||||
{
|
||||
throw new FactoryOrchestratorException("Unsupported guid type to poll!");
|
||||
}
|
||||
_guidType = guidType;
|
||||
}
|
||||
|
||||
private async void GetUpdatedObjectAsync(object state)
|
||||
{
|
||||
object newObj;
|
||||
try
|
||||
{
|
||||
if (_client.IsConnected)
|
||||
{
|
||||
// TODO: Logging: check for failure
|
||||
if ((_guidType == typeof(TaskBase)) || (_guidType == typeof(ExecutableTask)) || (_guidType == typeof(UWPTask)) || (_guidType == typeof(TAEFTest)))
|
||||
{
|
||||
newObj = await _client.QueryTask((Guid)PollingGuid);
|
||||
}
|
||||
else if (_guidType == typeof(TaskList))
|
||||
{
|
||||
if (PollingGuid != null)
|
||||
{
|
||||
newObj = await _client.QueryTaskList((Guid)PollingGuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
newObj = await _client.GetTaskListSummaries();
|
||||
}
|
||||
}
|
||||
else //if (_guidType == typeof(TaskRun))
|
||||
{
|
||||
newObj = await _client.QueryTaskRun((Guid)PollingGuid);
|
||||
}
|
||||
|
||||
if (!_stopped)
|
||||
{
|
||||
LatestObject = newObj;
|
||||
|
||||
if (!Equals(newObj, _lastEventObject))
|
||||
{
|
||||
if (_adaptiveInterval)
|
||||
{
|
||||
// Adaptive detects if the invoke method is taking too long. If it is, it increases the poll time by 10% of initial value.
|
||||
// Adaptive also throws away an invoke if it can't get the semaphore.
|
||||
int newInterval;
|
||||
if (_invokeSem.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
OnUpdatedObject?.Invoke(this, new ServerPollerEventArgs(newObj));
|
||||
}
|
||||
catch (Exception)
|
||||
{}
|
||||
|
||||
_lastEventObject = newObj;
|
||||
_invokeSem.Release();
|
||||
newInterval = Math.Max(_initialPollingInterval / _adaptiveModifier, _pollingInterval - _pollingIntervalStep);
|
||||
}
|
||||
else
|
||||
{
|
||||
newInterval = Math.Min(_initialPollingInterval * _adaptiveModifier, _pollingInterval + _pollingIntervalStep);
|
||||
}
|
||||
|
||||
if (newInterval != _pollingInterval)
|
||||
{
|
||||
_pollingInterval = newInterval;
|
||||
_timer?.Change(_pollingInterval, _pollingInterval);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// update object seen as it could be changed when the invoke returns
|
||||
_lastEventObject = newObj;
|
||||
OnUpdatedObject?.Invoke(this, new ServerPollerEventArgs(newObj));
|
||||
}
|
||||
catch (Exception)
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!OnlyRaiseOnExceptionEventForConnectionException || e.GetType() != typeof(FactoryOrchestratorConnectionException))
|
||||
{
|
||||
OnException?.Invoke(this, new ServerPollerExceptionHandlerArgs(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts polling the object.
|
||||
/// </summary>
|
||||
/// <param name="client">The FactoryOrchestratorClient object to use for polling.</param>
|
||||
public void StartPolling(FactoryOrchestratorClient client)
|
||||
{
|
||||
_client = client;
|
||||
|
||||
if (!_client.IsConnected)
|
||||
{
|
||||
throw new FactoryOrchestratorConnectionException("Start connection first!");
|
||||
}
|
||||
|
||||
if (_stopped != false)
|
||||
{
|
||||
_stopped = false;
|
||||
LatestObject = null;
|
||||
_lastEventObject = null;
|
||||
_timer = new Timer(GetUpdatedObjectAsync, null, 0, _pollingInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops polling the object.
|
||||
/// </summary>
|
||||
public void StopPolling()
|
||||
{
|
||||
_stopped = true;
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest object retrieved from the server.
|
||||
/// </summary>
|
||||
public object LatestObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The GUID of the object you are polling. Can be NULL for some scenarios.
|
||||
/// </summary>
|
||||
public Guid? PollingGuid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the poller is actively polling for updates.
|
||||
/// </summary>
|
||||
public bool IsPolling { get => !_stopped; }
|
||||
|
||||
private FactoryOrchestratorClient _client;
|
||||
private object _lastEventObject;
|
||||
private int _pollingInterval;
|
||||
private int _initialPollingInterval;
|
||||
private int _pollingIntervalStep;
|
||||
private Timer _timer;
|
||||
private SemaphoreSlim _invokeSem;
|
||||
private Type _guidType;
|
||||
private bool _stopped;
|
||||
private bool _adaptiveInterval;
|
||||
private int _adaptiveModifier;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a new object is received. It is only thrown if the object has changed since last polled.
|
||||
/// </summary>
|
||||
public event ServerPollerEventHandler OnUpdatedObject;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a poll attempt throws an exception.
|
||||
/// </summary>
|
||||
public event ServerPollerExceptionHandler OnException;
|
||||
|
||||
/// <summary>
|
||||
/// If true, OnException only raised when the exception is a FactoryOrchestratorConnectionException.
|
||||
/// Other exceptions are ignored!
|
||||
/// </summary>
|
||||
public bool OnlyRaiseOnExceptionEventForConnectionException { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class used to share the new object with the callee via OnUpdatedObject.
|
||||
/// </summary>
|
||||
public class ServerPollerEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new ServerPollerEventArgs instance.
|
||||
/// </summary>
|
||||
/// <param name="result">Object from latest poll of the Server.</param>
|
||||
public ServerPollerEventArgs(object result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The updated object polled on the server.
|
||||
/// </summary>
|
||||
public object Result { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler delegate for a when a new object has been retrieved from the Server.
|
||||
/// </summary>
|
||||
/// <param name="source">The ServerPoller that retrieved the object.</param>
|
||||
/// <param name="e">The result of the latest poll operation.</param>
|
||||
public delegate void ServerPollerEventHandler(object source, ServerPollerEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Event handler delegate for a when the poller hit an exception while polling.
|
||||
/// </summary>
|
||||
/// <param name="source">The ServerPoller that retrieved the object.</param>
|
||||
/// <param name="e">The exception from the latest poll operation.</param>
|
||||
public delegate void ServerPollerExceptionHandler(object source, ServerPollerExceptionHandlerArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Class containing the exception thrown from the latest poll operation.
|
||||
/// </summary>
|
||||
public class ServerPollerExceptionHandlerArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new ServerPollerExceptionArgs instance.
|
||||
/// </summary>
|
||||
/// <param name="exception">Exception from latest poll of the Server.</param>
|
||||
public ServerPollerExceptionHandlerArgs(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The updated object polled on the server.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
###############
|
||||
# temp file #
|
||||
###############
|
||||
*.yml
|
||||
.manifest
|
|
@ -0,0 +1,2 @@
|
|||
# Factory Orchestrator API Reference
|
||||
Use the links in the top or side navbars to browse the Factory Orchestrator client APIs.
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"metadata": [
|
||||
{
|
||||
"src": [
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"UWPClientLibrary/**",
|
||||
"ClientSample/**",
|
||||
"ServerLibrary/**",
|
||||
"Service/**",
|
||||
"IpcFramework/**",
|
||||
"App/**"
|
||||
],
|
||||
"src": "../"
|
||||
}
|
||||
],
|
||||
"dest": "api",
|
||||
"filter": "docfxfilter.yml",
|
||||
"disableGitFeatures": true,
|
||||
"disableDefaultFilter": false
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"api/**.yml",
|
||||
"api/index.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"toc.yml"
|
||||
]
|
||||
}
|
||||
],
|
||||
"resource": [
|
||||
{
|
||||
"files": [
|
||||
"images/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"overwrite": [
|
||||
{
|
||||
"files": [
|
||||
"apidoc/**.md"
|
||||
],
|
||||
"exclude": [
|
||||
"obj/**",
|
||||
"_site/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"globalMetadata": {
|
||||
"_appLogoPath": "images/fologo.png",
|
||||
"_appFaviconPath": "images/fologo.png"
|
||||
},
|
||||
"dest": "../bin/docfx",
|
||||
"globalMetadataFiles": [],
|
||||
"fileMetadataFiles": [],
|
||||
"template": [ "statictoc" ],
|
||||
"postProcessors": [],
|
||||
"markdownEngineName": "markdig",
|
||||
"noLangKeyword": false,
|
||||
"keepFileLink": false,
|
||||
"cleanupCacheHistory": false,
|
||||
"disableGitFeatures": true
|
||||
}
|
||||
}
|