diff --git a/.codex-reports/gui-html-files.txt b/.codex-reports/gui-html-files.txt
new file mode 100755
index 0000000000..9a68955ef9
--- /dev/null
+++ b/.codex-reports/gui-html-files.txt
@@ -0,0 +1,329 @@
+desktop/src/@batch-flask/ui/abstract-list/partial-sort-warning/partial-sort-warning.html
+desktop/src/@batch-flask/ui/activity/activity-monitor-footer-item/activity-monitor-footer-item.html
+desktop/src/@batch-flask/ui/activity/activity-monitor-footer/activity-monitor-footer.html
+desktop/src/@batch-flask/ui/activity/activity-monitor/activity-monitor-item/activity-monitor-item-action/activity-monitor-item-action.html
+desktop/src/@batch-flask/ui/activity/activity-monitor/activity-monitor-item/activity-monitor-item.html
+desktop/src/@batch-flask/ui/activity/activity-monitor/activity-monitor-tree-view/activity-monitor-tree-view.html
+desktop/src/@batch-flask/ui/activity/activity-monitor/activity-monitor.html
+desktop/src/@batch-flask/ui/advanced-filter/list-filter-control/list-filter-control.html
+desktop/src/@batch-flask/ui/advanced-filter/result-picker-control/result-picker-control.html
+desktop/src/@batch-flask/ui/advanced-filter/state-picker-control/state-picker-control.html
+desktop/src/@batch-flask/ui/banner/banner.html
+desktop/src/@batch-flask/ui/breadcrumbs/breadcrumb-group/breadcrumb-group.html
+desktop/src/@batch-flask/ui/breadcrumbs/breadcrumb/breadcrumb.html
+desktop/src/@batch-flask/ui/browse-layout/browse-layout.html
+desktop/src/@batch-flask/ui/browse-layout/toggle-filter-button/toggle-filter-button.html
+desktop/src/@batch-flask/ui/buttons/button.html
+desktop/src/@batch-flask/ui/buttons/directory-picker.html
+desktop/src/@batch-flask/ui/buttons/refresh-btn/refresh-btn.html
+desktop/src/@batch-flask/ui/callout/callout.html
+desktop/src/@batch-flask/ui/datetime-picker/datetime-picker.html
+desktop/src/@batch-flask/ui/dialogs/confirmation-dialog.html
+desktop/src/@batch-flask/ui/dialogs/prompt/prompt-dialog.html
+desktop/src/@batch-flask/ui/dropdown/dropdown.html
+desktop/src/@batch-flask/ui/duration-picker/duration-picker.html
+desktop/src/@batch-flask/ui/editor/editor.html
+desktop/src/@batch-flask/ui/entity-commands-list/button/entity-command-button.html
+desktop/src/@batch-flask/ui/entity-commands-list/entity-commands-list.html
+desktop/src/@batch-flask/ui/file/download-folder-dialog/download-folder-dialog.html
+desktop/src/@batch-flask/ui/file/file-explorer/file-explorer-tabs/file-explorer-tabs.html
+desktop/src/@batch-flask/ui/file/file-explorer/file-explorer.html
+desktop/src/@batch-flask/ui/file/file-explorer/file-table-view/file-path-navigator/file-path-navigator.html
+desktop/src/@batch-flask/ui/file/file-explorer/file-table-view/file-table-view.html
+desktop/src/@batch-flask/ui/file/file-explorer/file-tree-view/file-tree-view-row/file-tree-view-row.html
+desktop/src/@batch-flask/ui/file/file-explorer/file-tree-view/file-tree-view.html
+desktop/src/@batch-flask/ui/file/file-viewer/file-dialog-viewer/file-dialog-viewer.html
+desktop/src/@batch-flask/ui/file/file-viewer/file-too-large/file-too-large.html
+desktop/src/@batch-flask/ui/file/file-viewer/file-viewer-container/file-viewer-container.html
+desktop/src/@batch-flask/ui/file/file-viewer/file-viewer-container/file-viewer-header/file-viewer-header.html
+desktop/src/@batch-flask/ui/file/file-viewer/image-file-viewer/image-file-viewer.html
+desktop/src/@batch-flask/ui/file/file-viewer/log-file-viewer/log-file-viewer.html
+desktop/src/@batch-flask/ui/file/file-viewer/text-file-viewer/text-file-viewer.html
+desktop/src/@batch-flask/ui/form/complex-form/complex-form.html
+desktop/src/@batch-flask/ui/form/complex-form/footer/form-footer.html
+desktop/src/@batch-flask/ui/form/editable-table/editable-table.html
+desktop/src/@batch-flask/ui/form/editable-table/select-cell/editable-table-select-cell.html
+desktop/src/@batch-flask/ui/form/expanding-textarea/expanding-textarea.html
+desktop/src/@batch-flask/ui/form/form-field/form-field.html
+desktop/src/@batch-flask/ui/form/form-json-editor/form-json-editor.html
+desktop/src/@batch-flask/ui/form/form-page/form-page.html
+desktop/src/@batch-flask/ui/form/form-picker/form-multi-picker.html
+desktop/src/@batch-flask/ui/form/form-picker/form-picker.html
+desktop/src/@batch-flask/ui/form/form-section/form-section.html
+desktop/src/@batch-flask/ui/form/key-value-picker/key-value-picker.html
+desktop/src/@batch-flask/ui/form/simple-form/simple-form.html
+desktop/src/@batch-flask/ui/form/slide-toggle/slide-toggle.html
+desktop/src/@batch-flask/ui/graphs/gauge/gauge.html
+desktop/src/@batch-flask/ui/i18n/i18n.html
+desktop/src/@batch-flask/ui/icon/icon.html
+desktop/src/@batch-flask/ui/info-box/info-box.html
+desktop/src/@batch-flask/ui/keybindings/keybinding-picker/keybinding-picker-dialog.html
+desktop/src/@batch-flask/ui/keybindings/keybindings.html
+desktop/src/@batch-flask/ui/list-and-show-layout/entity-details-list.html
+desktop/src/@batch-flask/ui/loading/loading.html
+desktop/src/@batch-flask/ui/metrics-monitor/metrics-monitor-metric/metrics-monitor-metric.html
+desktop/src/@batch-flask/ui/metrics-monitor/metrics-monitor.html
+desktop/src/@batch-flask/ui/notifications/persisted-dropdown/persisted-notifications-dropdown.html
+desktop/src/@batch-flask/ui/notifications/toast/toast.html
+desktop/src/@batch-flask/ui/notifications/toasts-container/toasts-container.html
+desktop/src/@batch-flask/ui/property-list/bool-property/bool-property.html
+desktop/src/@batch-flask/ui/property-list/date-property/date-property.html
+desktop/src/@batch-flask/ui/property-list/entity-configuration/entity-configuration.html
+desktop/src/@batch-flask/ui/property-list/link-property/link-property.html
+desktop/src/@batch-flask/ui/property-list/property-content/property-content.html
+desktop/src/@batch-flask/ui/property-list/property-field/property-field.html
+desktop/src/@batch-flask/ui/property-list/property-group/property-group.html
+desktop/src/@batch-flask/ui/property-list/table-property/table-property.html
+desktop/src/@batch-flask/ui/property-list/text-property/text-property.html
+desktop/src/@batch-flask/ui/quick-list/quick-list-row-render/quick-list-row-render.html
+desktop/src/@batch-flask/ui/quick-list/quick-list.html
+desktop/src/@batch-flask/ui/quotas/quota-display/quota-display.html
+desktop/src/@batch-flask/ui/scrollable/scrollable.html
+desktop/src/@batch-flask/ui/select/select-dropdown/select-dropdown.html
+desktop/src/@batch-flask/ui/select/select.html
+desktop/src/@batch-flask/ui/server-error/server-error.html
+desktop/src/@batch-flask/ui/sidebar/sidebar-bookmarks/sidebar-bookmarks.html
+desktop/src/@batch-flask/ui/sidebar/sidebar-content/sidebar-content.html
+desktop/src/@batch-flask/ui/simple-dialog/simple-dialog.html
+desktop/src/@batch-flask/ui/split-pane/split-pane.html
+desktop/src/@batch-flask/ui/split-pane/split-separator/split-separator.html
+desktop/src/@batch-flask/ui/summary-card/summary-card.html
+desktop/src/@batch-flask/ui/table/table-head/table-head-cell/table-head-cell.html
+desktop/src/@batch-flask/ui/table/table-head/table-head.html
+desktop/src/@batch-flask/ui/table/table-row-render/table-row-render.html
+desktop/src/@batch-flask/ui/table/table.html
+desktop/src/@batch-flask/ui/tabs/tab-group.html
+desktop/src/@batch-flask/ui/tags/tag-list/tag-list.html
+desktop/src/@batch-flask/ui/tags/tags.html
+desktop/src/@batch-flask/ui/time-range-picker/time-range-picker.html
+desktop/src/@batch-flask/ui/toolbar/toolbar.html
+desktop/src/@batch-flask/ui/virtual-scroll/virtual-scroll-tail/virtual-scroll-tail.html
+desktop/src/@batch-flask/ui/virtual-scroll/virtual-scroll.html
+desktop/src/@batch-flask/ui/vtabs/vtab-group.html
+desktop/src/@batch-flask/ui/vtabs/vtab.html
+desktop/src/app/app.layout.html
+desktop/src/app/components/account/action/add/add-local-batch-account.html
+desktop/src/app/components/account/action/create/batch-account-create.html
+desktop/src/app/components/account/action/delete/delete-account-dialog.html
+desktop/src/app/components/account/action/edit-storage-account/edit-storage-account-form.html
+desktop/src/app/components/account/base/auto-storage-account-picker/auto-storage-account-picker.html
+desktop/src/app/components/account/browse/account-dropdown.html
+desktop/src/app/components/account/browse/account-list/account-list.html
+desktop/src/app/components/account/details/account-cost-card/account-cost-card.html
+desktop/src/app/components/account/details/account-details.html
+desktop/src/app/components/account/details/account-monitoring-section/account-monitoring-section.html
+desktop/src/app/components/account/details/account-quotas-card/account-quotas-card.html
+desktop/src/app/components/account/details/account-summary-card/account-summary-card.html
+desktop/src/app/components/account/details/getting-started-card/getting-started-card.html
+desktop/src/app/components/account/details/programmatic-usage/aad-credentials-picker/aad-app-picker/aad-app-picker.html
+desktop/src/app/components/account/details/programmatic-usage/aad-credentials-picker/aad-app-secret-picker/aad-app-secret-picker.html
+desktop/src/app/components/account/details/programmatic-usage/aad-credentials-picker/aad-credentials-picker.html
+desktop/src/app/components/account/details/programmatic-usage/aad-credentials-picker/create-new-aad-app/create-new-aad-app.html
+desktop/src/app/components/account/details/programmatic-usage/aad-credentials-picker/generate-aad-app-secret/generate-aad-app-secret.html
+desktop/src/app/components/account/details/programmatic-usage/aad-credentials-picker/resource-permission-button/resource-permission-button.html
+desktop/src/app/components/account/details/programmatic-usage/programmatic-usage.html
+desktop/src/app/components/account/details/programmatic-usage/programming-sample/programming-sample.html
+desktop/src/app/components/account/details/storage-account-card/storage-account-card.html
+desktop/src/app/components/account/home/account-home.html
+desktop/src/app/components/account/monitoring/account-monitoring-home/account-monitoring-home.html
+desktop/src/app/components/account/monitoring/monitor-chart/monitor-chart.html
+desktop/src/app/components/application/action/create/application-create-dialog.html
+desktop/src/app/components/application/action/edit/application-edit-dialog.html
+desktop/src/app/components/application/browse/application-list.html
+desktop/src/app/components/application/browse/preview/application-preview.html
+desktop/src/app/components/application/details/application-details.html
+desktop/src/app/components/application/details/application-package-table.html
+desktop/src/app/components/application/details/application-packages.html
+desktop/src/app/components/application/details/configuration/application-configuration.html
+desktop/src/app/components/application/errors/application-error-display.html
+desktop/src/app/components/application/home/application-home.html
+desktop/src/app/components/auth/auth-overlay/auth-overlay.html
+desktop/src/app/components/auth/external-browser-auth-toggle.html
+desktop/src/app/components/certificate/action/add/add-certificate-form.html
+desktop/src/app/components/certificate/action/reactivate/reactivate-certificate-dialog.html
+desktop/src/app/components/certificate/browse/certificate-list.html
+desktop/src/app/components/certificate/browse/filter/certificate-advanced-filter.html
+desktop/src/app/components/certificate/details/certificate-configuration.html
+desktop/src/app/components/certificate/details/certificate-details.html
+desktop/src/app/components/certificate/home/certificate-home.html
+desktop/src/app/components/common/blob-container-picker/blob-container-picker.html
+desktop/src/app/components/common/edit-metadata-form/edit-metadata-form.html
+desktop/src/app/components/common/guards/select-acccount-dialog/select-account-dialog.html
+desktop/src/app/components/common/inline-quota/inline-quota.html
+desktop/src/app/components/common/location-picker/location-picker.html
+desktop/src/app/components/common/location/location.html
+desktop/src/app/components/common/react-action-form/react-action-form.html
+desktop/src/app/components/common/resource-files-properties/resource-files-properties.html
+desktop/src/app/components/common/resourcefile-picker/resourcefile-cloud-file-dialog/resourcefile-cloud-file-dialog.html
+desktop/src/app/components/common/resourcefile-picker/resourcefile-container-source/resourcefile-container-source.html
+desktop/src/app/components/common/resourcefile-picker/resourcefile-picker-row/resourcefile-picker-row.html
+desktop/src/app/components/common/resourcefile-picker/resourcefile-picker.html
+desktop/src/app/components/common/storage-account-picker/storage-account-picker.html
+desktop/src/app/components/common/subscription-picker/subscription-picker.html
+desktop/src/app/components/data/action/add/file-group-create-form.html
+desktop/src/app/components/data/action/add/file-group-options-picker.html
+desktop/src/app/components/data/action/delete/delete-container-dialog.html
+desktop/src/app/components/data/browse/data-container-list.html
+desktop/src/app/components/data/details/data-container-configuration.html
+desktop/src/app/components/data/details/data-container-files.html
+desktop/src/app/components/data/details/data-details.html
+desktop/src/app/components/data/home/data-home.html
+desktop/src/app/components/data/shared/cloud-file-picker/cloud-file-picker-dialog.html
+desktop/src/app/components/data/shared/cloud-file-picker/cloud-file-picker.html
+desktop/src/app/components/data/shared/errors/storage-error-display.html
+desktop/src/app/components/data/shared/file-group-picker/file-group-picker.html
+desktop/src/app/components/data/shared/file-group-sas/file-group-sas.html
+desktop/src/app/components/data/shared/file-groups-picker/file-groups-picker.html
+desktop/src/app/components/data/shared/file-or-directory-picker/file-or-directory-picker.html
+desktop/src/app/components/data/shared/job-id/job-id.html
+desktop/src/app/components/file/browse/blob-files-browser/blob-files-browser.html
+desktop/src/app/components/file/browse/node-file-browse.html
+desktop/src/app/components/job-schedule/action/add/job-schedule-create-basic-dialog.html
+desktop/src/app/components/job-schedule/action/add/job-schedule-job-specification.html
+desktop/src/app/components/job-schedule/action/delete/delete-job-schedule-dialog.html
+desktop/src/app/components/job-schedule/action/disable/disable-job-schedule-dialog.html
+desktop/src/app/components/job-schedule/action/enable/enable-job-schedule-dialog.html
+desktop/src/app/components/job-schedule/action/terminate/terminate-job-schedule-dialog.html
+desktop/src/app/components/job-schedule/browse/filter/job-schedule-advanced-filter.html
+desktop/src/app/components/job-schedule/browse/job-schedule-list.html
+desktop/src/app/components/job-schedule/details/autopool/job-schedule-autopool.html
+desktop/src/app/components/job-schedule/details/job-schedule-configuration.html
+desktop/src/app/components/job-schedule/details/job-schedule-details.html
+desktop/src/app/components/job-schedule/details/job-schedule-job-specification.html
+desktop/src/app/components/job-schedule/home/job-schedule-home.html
+desktop/src/app/components/job/action/add/add-job-form.html
+desktop/src/app/components/job/action/add/job-manager-task-picker.html
+desktop/src/app/components/job/action/add/job-preparation-task-picker.html
+desktop/src/app/components/job/action/add/job-release-task-picker.html
+desktop/src/app/components/job/action/add/pool-picker/pool-picker.html
+desktop/src/app/components/job/action/disable/disable-job-dialog.html
+desktop/src/app/components/job/base/job-state/job-state.html
+desktop/src/app/components/job/browse/filter/job-advanced-filter.html
+desktop/src/app/components/job/browse/job-list.html
+desktop/src/app/components/job/details/error-display/job-error-display.html
+desktop/src/app/components/job/details/job-configuration.html
+desktop/src/app/components/job/details/job-details.html
+desktop/src/app/components/job/details/job-progress-status/job-progress-status.html
+desktop/src/app/components/job/graphs/all-job-graphs-home/all-job-graphs-home.html
+desktop/src/app/components/job/graphs/all-job-graphs-home/jobs-bar-chart/jobs-bar-chart.html
+desktop/src/app/components/job/graphs/all-job-graphs-home/jobs-cpu-wait-time-graph/jobs-cpu-wait-time-graph.html
+desktop/src/app/components/job/graphs/all-job-graphs-home/jobs-running-time-graph/jobs-running-time-graph.html
+desktop/src/app/components/job/graphs/job-graphs-home/job-graphs-home.html
+desktop/src/app/components/job/graphs/job-progress-graph/job-progress-graph.html
+desktop/src/app/components/job/graphs/tasks-running-time-graph/tasks-running-time-graph.html
+desktop/src/app/components/job/home/job-home.html
+desktop/src/app/components/job/job-hook-task/job-hook-task-browser/job-hook-task-browser.html
+desktop/src/app/components/job/job-hook-task/job-hook-task-details/job-hook-task-details.html
+desktop/src/app/components/layout/footer/footer.html
+desktop/src/app/components/layout/footer/timezone-dropdown/timezone-dropdown.html
+desktop/src/app/components/layout/footer/version-channel/version-type.html
+desktop/src/app/components/layout/header/header.html
+desktop/src/app/components/layout/main-navigation/main-navigation.html
+desktop/src/app/components/layout/main-navigation/profile-button/profile-button.html
+desktop/src/app/components/layout/online-status/online-status.html
+desktop/src/app/components/layout/pinned-entity-dropdown/pinned-dropdown.html
+desktop/src/app/components/misc/playground-route/playground-route.html
+desktop/src/app/components/misc/theme-colors/theme-colors.html
+desktop/src/app/components/node/action/upload-node-logs/upload-node-logs-dialog.html
+desktop/src/app/components/node/browse/display/node-list-display.html
+desktop/src/app/components/node/browse/filter/node-advanced-filter.html
+desktop/src/app/components/node/browse/node-list.html
+desktop/src/app/components/node/connect/node-connect.html
+desktop/src/app/components/node/connect/property-display/node-property-display.html
+desktop/src/app/components/node/connect/ssh-key-picker-dialog/ssh-key-picker-dialog.html
+desktop/src/app/components/node/connect/ssh-key-picker/ssh-key-picker.html
+desktop/src/app/components/node/details/configuration/node-configuration.html
+desktop/src/app/components/node/details/error/node-error-display.html
+desktop/src/app/components/node/details/error/start-task-error-display.html
+desktop/src/app/components/node/details/node-details.html
+desktop/src/app/components/node/home/node-home.html
+desktop/src/app/components/pool/action/add/container-configuration-picker/container-configuration-picker.html
+desktop/src/app/components/pool/action/add/container-configuration-picker/images-picker/container-images-picker.html
+desktop/src/app/components/pool/action/add/container-configuration-picker/registry-picker/container-registry-picker.html
+desktop/src/app/components/pool/action/add/os-picker/data-disk-picker/data-disk-picker.html
+desktop/src/app/components/pool/action/add/os-picker/os-image-picker/os-image-picker.html
+desktop/src/app/components/pool/action/add/os-picker/os-offer-tile/os-offer-tile.html
+desktop/src/app/components/pool/action/add/os-picker/pool-os-picker.html
+desktop/src/app/components/pool/action/add/os-picker/sig-image-picker/sig-image-picker.html
+desktop/src/app/components/pool/action/add/pool-create-basic-dialog.html
+desktop/src/app/components/pool/action/add/vm-size-picker/vm-size-picker-filter.html
+desktop/src/app/components/pool/action/add/vm-size-picker/vm-size-picker.html
+desktop/src/app/components/pool/action/delete/delete-pool-dialog.html
+desktop/src/app/components/pool/action/edit/edit-app-package/edit-app-package-form.html
+desktop/src/app/components/pool/action/edit/edit-certificate/edit-certificate-references-form.html
+desktop/src/app/components/pool/action/edit/edit-node-comms/edit-node-comms-form.html
+desktop/src/app/components/pool/action/resize/pool-resize-dialog.html
+desktop/src/app/components/pool/action/scale/autoscale-formula-picker/autoscale-formula-picker.html
+desktop/src/app/components/pool/action/scale/autoscale-formula-picker/evaluate-autoscale-formula/evaluate-autoscale-formula.html
+desktop/src/app/components/pool/action/scale/deallocation-option-picker.html
+desktop/src/app/components/pool/action/scale/pool-scale-picker/pool-scale-picker.html
+desktop/src/app/components/pool/base/app-packages/app-package-picker.html
+desktop/src/app/components/pool/base/certificate-references/certificate-picker/certificate-picker.html
+desktop/src/app/components/pool/base/certificate-references/certificate-references-picker.html
+desktop/src/app/components/pool/base/pool-nodes-preview.html
+desktop/src/app/components/pool/base/pool-os-icon/pool-os-icon.html
+desktop/src/app/components/pool/browse/filter/pool-advanced-filter.html
+desktop/src/app/components/pool/browse/pool-list.html
+desktop/src/app/components/pool/details/configuration/pool-configuration.html
+desktop/src/app/components/pool/details/error-display/pool-error-display.html
+desktop/src/app/components/pool/details/pool-cost-card/pool-cost-card.html
+desktop/src/app/components/pool/details/pool-details.html
+desktop/src/app/components/pool/graphs/heatmap/legend/nodes-heatmap-legend.html
+desktop/src/app/components/pool/graphs/heatmap/nodes-heatmap.html
+desktop/src/app/components/pool/graphs/history-graph/history-graph.html
+desktop/src/app/components/pool/graphs/node-preview-card.html
+desktop/src/app/components/pool/graphs/performance-graph/cpu-usage/cpu-usage-graph.html
+desktop/src/app/components/pool/graphs/performance-graph/disk-io/disk-io-graph.html
+desktop/src/app/components/pool/graphs/performance-graph/disk-usage/disk-usage-graph.html
+desktop/src/app/components/pool/graphs/performance-graph/enable-app-insights-doc/enable-app-insights-doc.html
+desktop/src/app/components/pool/graphs/performance-graph/gpu-memory-usage/gpu-memory-usage-graph.html
+desktop/src/app/components/pool/graphs/performance-graph/gpu-usage/gpu-usage-graph.html
+desktop/src/app/components/pool/graphs/performance-graph/memory-usage/memory-usage-graph.html
+desktop/src/app/components/pool/graphs/performance-graph/network-usage/network-usage-graph.html
+desktop/src/app/components/pool/graphs/performance-graph/performance-graph.html
+desktop/src/app/components/pool/graphs/pool-graphs.html
+desktop/src/app/components/pool/graphs/pool-state-graph/pool-state-graph.html
+desktop/src/app/components/pool/graphs/standalone/pool-standalone-graphs.html
+desktop/src/app/components/pool/home/pool-home.html
+desktop/src/app/components/pool/network-configuration/inbound-nat-pool-picker.html
+desktop/src/app/components/pool/network-configuration/network-security-group-rules.html
+desktop/src/app/components/pool/network-configuration/virtual-network-picker/virtual-network-picker.html
+desktop/src/app/components/pool/start-task/start-task-edit-form.html
+desktop/src/app/components/pool/start-task/start-task-picker.html
+desktop/src/app/components/pool/user-accounts-picker/user-account-picker/user-account-picker.html
+desktop/src/app/components/pool/user-accounts-picker/user-accounts-picker.html
+desktop/src/app/components/settings/auth-settings/auth-settings.html
+desktop/src/app/components/settings/settings.html
+desktop/src/app/components/task/action/add/add-task-form.html
+desktop/src/app/components/task/action/add/multi-instance-settings-picker/multi-instance-settings-picker.html
+desktop/src/app/components/task/base/container-settings/container-settings-picker.html
+desktop/src/app/components/task/base/container-settings/registry-picker.html
+desktop/src/app/components/task/base/task-state/task-state.html
+desktop/src/app/components/task/base/user-identity/user-identity-picker.html
+desktop/src/app/components/task/browse/filter/task-advanced-filter.html
+desktop/src/app/components/task/browse/preview/task-preview.html
+desktop/src/app/components/task/browse/preview/task-runtime.html
+desktop/src/app/components/task/browse/task-list.html
+desktop/src/app/components/task/details/configuration/task-configuration.html
+desktop/src/app/components/task/details/output/task-outputs.html
+desktop/src/app/components/task/details/sub-tasks/list/sub-task-list.html
+desktop/src/app/components/task/details/sub-tasks/properties/sub-task-properties.html
+desktop/src/app/components/task/details/sub-tasks/sub-tasks-browser.html
+desktop/src/app/components/task/details/task-dependency-browser/task-dependency-browser.html
+desktop/src/app/components/task/details/task-details.html
+desktop/src/app/components/task/details/task-error-display.html
+desktop/src/app/components/task/details/task-node-info/task-node-info.html
+desktop/src/app/components/task/details/task-timeline/task-timeline-state.html
+desktop/src/app/components/task/details/task-timeline/task-timeline.html
+desktop/src/app/components/task/home/task-home.html
+desktop/src/app/components/tenant-picker/tenant-card.html
+desktop/src/app/components/tenant-picker/tenant-picker.html
+desktop/src/app/components/welcome/welcome.html
+desktop/src/app/components/workspace/workspace-selector/workspace-dropdown.html
+desktop/src/app/index.html
+desktop/src/client/proxy/manual-proxy-configuration-window/manual-proxy-configuration-window.html
+desktop/src/client/proxy/proxy-credentials-window/proxy-credentials.html
+desktop/src/client/recover-window/recover-window.html
+desktop/src/client/splash-screen/splash-screen.html
+desktop/test/app/memory-leak/test-big-component.html
+web/dev-server/index.html
diff --git a/.codex-reports/gui-react-files.txt b/.codex-reports/gui-react-files.txt
new file mode 100755
index 0000000000..49d49e2540
--- /dev/null
+++ b/.codex-reports/gui-react-files.txt
@@ -0,0 +1,111 @@
+packages/bonito-ui/src/components/__tests__/button.spec.tsx
+packages/bonito-ui/src/components/__tests__/data-grid.spec.tsx
+packages/bonito-ui/src/components/action/__tests__/action-bar.spec.tsx
+packages/bonito-ui/src/components/action/action-bar.tsx
+packages/bonito-ui/src/components/button.tsx
+packages/bonito-ui/src/components/data-grid/data-grid.tsx
+packages/bonito-ui/src/components/editor/MonacoEditor.tsx
+packages/bonito-ui/src/components/editor/__tests__/MonacoEditor.spec.tsx
+packages/bonito-ui/src/components/editor/impl/MonacoEditorImpl.tsx
+packages/bonito-ui/src/components/editor/impl/MonacoWrapper.tsx
+packages/bonito-ui/src/components/example/__tests__/simple-example.spec.tsx
+packages/bonito-ui/src/components/example/simple-example.tsx
+packages/bonito-ui/src/components/form/__tests__/action-form.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/checkbox.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/dropdown.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/form-container.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/list-form-layout.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/location-dropdown.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/radio-button.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/resource-group-dropdown.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/storage-account-dropdown.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/string-list.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/subscription-dropdown.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/tab-selector.spec.tsx
+packages/bonito-ui/src/components/form/__tests__/text-field.spec.tsx
+packages/bonito-ui/src/components/form/action-form.tsx
+packages/bonito-ui/src/components/form/checkbox.tsx
+packages/bonito-ui/src/components/form/default-form-control-resolver.tsx
+packages/bonito-ui/src/components/form/dropdown.tsx
+packages/bonito-ui/src/components/form/form-container.tsx
+packages/bonito-ui/src/components/form/list-form-layout.tsx
+packages/bonito-ui/src/components/form/location-dropdown.tsx
+packages/bonito-ui/src/components/form/radio-button.tsx
+packages/bonito-ui/src/components/form/resource-group-dropdown.tsx
+packages/bonito-ui/src/components/form/storage-account-dropdown.tsx
+packages/bonito-ui/src/components/form/string-list.tsx
+packages/bonito-ui/src/components/form/subscription-dropdown.tsx
+packages/bonito-ui/src/components/form/tab-selector.tsx
+packages/bonito-ui/src/components/form/text-field.tsx
+packages/bonito-ui/src/components/layout/content-pane.tsx
+packages/bonito-ui/src/components/layout/display-pane.tsx
+packages/bonito-ui/src/components/layout/root-pane.tsx
+packages/bonito-ui/src/components/layout/tab-container.tsx
+packages/bonito-ui/src/components/panel/__tests__/panel.spec.tsx
+packages/bonito-ui/src/components/panel/panel-footer.tsx
+packages/bonito-ui/src/components/panel/panel.tsx
+packages/bonito-ui/src/components/property/__tests__/property-field.spec.tsx
+packages/bonito-ui/src/components/property/__tests__/property-group.spec.tsx
+packages/bonito-ui/src/components/property/__tests__/text-property.spec.tsx
+packages/bonito-ui/src/components/property/date-property.tsx
+packages/bonito-ui/src/components/property/property-field.tsx
+packages/bonito-ui/src/components/property/property-group.tsx
+packages/bonito-ui/src/components/property/property-list.tsx
+packages/bonito-ui/src/components/property/text-property.tsx
+packages/bonito-ui/src/form/__tests__/react-form.spec.tsx
+packages/bonito-ui/src/form/internal/react-form-impl.tsx
+packages/bonito-ui/src/test-util/__tests__/a11y.spec.tsx
+packages/playground/src/__tests__/button-demo.spec.tsx
+packages/playground/src/__tests__/playground-example.spec.tsx
+packages/playground/src/__tests__/setup-tests.tsx
+packages/playground/src/demo-routes.tsx
+packages/playground/src/demo/display/certificate/certificate-display-demo.tsx
+packages/playground/src/demo/display/task-grid/task-data-grid.tsx
+packages/playground/src/demo/display/vm-extension/vm-extension-list-demo.tsx
+packages/playground/src/demo/form/action-form-demo.tsx
+packages/playground/src/demo/form/button/button-demo.tsx
+packages/playground/src/demo/form/checkbox/checkbox-demo.tsx
+packages/playground/src/demo/form/combobox/combobox-demo.tsx
+packages/playground/src/demo/form/dropdown-demo.tsx
+packages/playground/src/demo/form/notification-demo.tsx
+packages/playground/src/demo/form/radiobutton/radiobutton-demo.tsx
+packages/playground/src/demo/form/searchbox/searchbox-demo.tsx
+packages/playground/src/demo/form/stringlist/stringlist-demo.tsx
+packages/playground/src/demo/form/tab-selector-demo.tsx
+packages/playground/src/demo/form/textfield/textfield-demo.tsx
+packages/playground/src/functions.tsx
+packages/playground/src/layout/demo-component-container.tsx
+packages/playground/src/layout/demo-control-container.tsx
+packages/playground/src/layout/demo-main-content.tsx
+packages/playground/src/layout/demo-nav-menu.tsx
+packages/playground/src/layout/demo-pane.tsx
+packages/playground/src/playground-example.tsx
+packages/playground/src/style.tsx
+packages/react/src/account/__tests__/create-account-action.spec.tsx
+packages/react/src/components/certificate/certificate-display.tsx
+packages/react/src/components/certificate/certificate-list.tsx
+packages/react/src/components/certificate/certificate-page.tsx
+packages/react/src/components/certificate/certificate-property-list.tsx
+packages/react/src/form/batch-form-control-resolver.tsx
+packages/react/src/pool/__tests__/node-comms-dropdown.spec.tsx
+packages/react/src/pool/__tests__/update-node-comms-action.spec.tsx
+packages/react/src/pool/node-comms-dropdown.tsx
+packages/react/src/pool/node-comms-parameter.tsx
+packages/react/src/pool/update-node-comms-action.tsx
+packages/react/src/vm-extension/__tests__/node-vm-ext-list.spec.tsx
+packages/react/src/vm-extension/__tests__/pool-vm-ext-list.spec.tsx
+packages/react/src/vm-extension/__tests__/vm-extension-details-panel.spec.tsx
+packages/react/src/vm-extension/__tests__/vm-extension-details.spec.tsx
+packages/react/src/vm-extension/__tests__/vm-extension-list.spec.tsx
+packages/react/src/vm-extension/node-vm-ext-list.tsx
+packages/react/src/vm-extension/pool-vm-ext-list.tsx
+packages/react/src/vm-extension/vm-extension-details-panel.tsx
+packages/react/src/vm-extension/vm-extension-details.tsx
+packages/react/src/vm-extension/vm-extension-list.tsx
+web/src/components/__tests__/application.spec.tsx
+web/src/components/application.tsx
+web/src/components/layout/app-root.tsx
+web/src/components/layout/footer.tsx
+web/src/components/layout/header.tsx
+web/src/components/layout/main.tsx
+web/src/index.tsx
diff --git a/.codex-reports/npm-scripts-all.txt b/.codex-reports/npm-scripts-all.txt
new file mode 100755
index 0000000000..bb6c9e6174
--- /dev/null
+++ b/.codex-reports/npm-scripts-all.txt
@@ -0,0 +1,391 @@
+C:\Users\m4x\BatchExplorer\desktop\package.json
+ ts: ts-node --project tsconfig.node.json --files
+ ts:fast: npm run ts -T
+ start: npm run electron:prod
+ clean: rimraf build/* dll/* src/generated/*
+ karma: cross-env NODE_OPTIONS=--openssl-legacy-provider node --max_old_space_size=4096 node_modules/karma/bin/karma
+ test: npm run test-client:coverage && npm run test-app
+ test:all: cross-env BE_ENABLE_A11Y_TESTING=true npm run test-client:coverage && npm run test-app
+ test-app:a11y: cross-env BE_ENABLE_A11Y_TESTING=true npm run test-app
+ test-app: cross-env COVERAGE=1 npm run karma -- start
+ test-app:mem: cross-env DEBUG_MEM=1 npm run test-app
+ test-app-watch: npm run karma -- start --auto-watch --no-single-run
+ test-app-watch:time: cross-env DEBUG_TIME=1 npm run test-app-watch
+ test-browser: npm run test-app
+ test-browser-watch: npm run test-app-watch
+ test-client: cross-env NODE_ENV=test node test/client/run-jasmine.js
+ test-client:coverage: nyc -e .ts -x "*.spec.ts" npm run test-client && nyc report --reporter=text-lcov > coverage.lcov
+ test-client-watch: npm run test-client -- --watch
+ test-e2e: playwright test
+ test-e2e:debug: playwright test --debug
+ test-models: npm run ts scripts/swagger/validate-models.ts
+ build-client: tsc -p tsconfig.node.json
+ build-client:dev: tsc -p tsconfig.node.json --noUnusedLocals false
+ build-app: npm run webpack -- --profile
+ build: npm run clean && npm run build-translations && npm run build-client && npm run build-app
+ build:clean: npm run build
+ build:package: npm run build:prod && npm run package
+ build:prod: cross-env NODE_ENV=production npm run build
+ build:test: npm run build && npm run test
+ build-translations: bux build-translations --src src --dest i18n --outputPath resources/i18n
+ watch: npm run webpack -- --watch --progress --profile
+ electron: electron build/client/main.js
+ electron:prod: cross-env NODE_ENV=production electron build/client/main.js
+ dev: concurrently --kill-others "npm run dev-server" "npm run dev-electron"
+ dev-electron: cross-env HOT=1 electron build/client/main.js
+ dev-server: npm run webpack-dev-server
+ eslint: eslint -c .eslintrc.js . --quiet
+ eslint:verbose: eslint -c .eslintrc.js .
+ stylelint: stylelint --custom-syntax postcss-scss "src/app/**/*.scss"
+ lint: npm run eslint && npm run stylelint
+ lint:fix: npm run eslint --fix && npm run stylelint
+ package: npm run ts scripts/package/package.ts
+ postinstall: npm run rebuild:app-deps && npx patch-package
+ start-publish: npm run ts scripts/publish/publish.ts
+ webpack: cross-env node --trace-deprecation --max_old_space_size=4096 node_modules/webpack/bin/webpack.js
+ webpack:stats: cross-env NODE_ENV=production NODE_OPTIONS=--openssl-legacy-provider npm run webpack -- --profile --json > coverage/webpack-stats.json
+ webpack-dev-server: cross-env NODE_OPTIONS=--openssl-legacy-provider node --trace-deprecation --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js
+ rebuild:app-deps: electron-builder install-app-deps
+ workspace:build: npm run build
+ workspace:build:clean: npm run build
+ workspace:build:desktop: npm run build
+ workspace:build:desktop:client: npm run build-client:dev
+ workspace:build:package: npm run build:package
+ workspace:build:prod: npm run build:prod
+ workspace:build:test: npm run build:test
+ workspace:build-translations: npm run build-translations
+ workspace:clean: npm run clean
+ workspace:launch:desktop: npm run dev
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint:fix
+ workspace:start:desktop: npm run dev-server
+ workspace:test: npm run test
+ workspace:test:desktop: npm run test
+ workspace:test:all: npm run test:all
+
+C:\Users\m4x\BatchExplorer\package.json
+ build: lerna run workspace:build --stream
+ build:clean: lerna run workspace:build:clean --stream
+ build:desktop: lerna run workspace:build:desktop --stream
+ build:desktop:client: lerna run workspace:build:desktop:client --stream
+ build:lib: lerna run workspace:build:lib --stream
+ build:web: lerna run workspace:build:web --stream
+ build:package: lerna run workspace:build:package --stream
+ build:prod: lerna run workspace:build:prod --stream
+ build:test: lerna run workspace:build:test --stream
+ build-translations: lerna run workspace:build-translations --stream
+ clean: lerna run --parallel workspace:clean --stream && bux rmrf ./Localize/out
+ gather-build-results: bux gather-build-results
+ launch: npm run -s launch:web
+ launch:desktop: lerna run --parallel workspace:launch:desktop --stream
+ launch:dev-electron: cd desktop && npm run dev-electron
+ launch:web: lerna run --parallel workspace:launch:web --stream
+ lint: prettier -c . && lerna run --parallel workspace:lint --stream && npm run -s lint:markdown
+ lint:fix: prettier -w . && lerna run --parallel workspace:lint:fix --stream && npm run -s lint:markdown
+ lint:markdown: markdownlint-cli2 "**/*.md" "#**/node_modules/**/*" "#SECURITY.md"
+ loc:build: pwsh -ExecutionPolicy Bypass -File ./Localize/build.ps1 && pwsh -ExecutionPolicy Bypass -File ./Localize/copy-translations.ps1
+ loc:restore: cd Localize && pwsh -ExecutionPolicy Bypass -File restore.ps1
+ start: npm run -s start:web
+ start:desktop: lerna run --parallel workspace:start:desktop --stream
+ start:web: lerna run --parallel workspace:start:web --stream
+ test: lerna run workspace:test --stream && npm run gather-build-results
+ test:all: lerna run workspace:test:all --stream && npm run gather-build-results
+ test:desktop: lerna run workspace:test:desktop --stream && npm run gather-build-results
+ test:lib: lerna run workspace:test:lib --stream && npm run gather-build-results
+ test:lib:all: lerna run workspace:test:lib:all --stream && npm run gather-build-results
+ test:web: lerna run workspace:test:web --stream && npm run gather-build-results
+ test:web:all: lerna run workspace:test:web:all --stream && npm run gather-build-results
+ postinstall: lerna bootstrap -- --legacy-peer-deps && node ./util/bux/check-cli-path
+ dev-setup: cd ./util/bux && npm pack --pack-destination ./build && npm install -g ./build/batch-bux-1.0.0.tgz && echo 'bux CLI tool installed'
+ watch:lib: lerna run --parallel workspace:watch:lib --stream
+
+C:\Users\m4x\BatchExplorer\packages\bonito-core\package.json
+ build: npm run build-translations && npm run compile
+ build:clean: run-s clean build
+ build:test: run-s build test
+ build-translations: bux build-translations --src src --dest i18n --outputPath resources/i18n/json --packageName bonito.core
+ compile: run-p compile:*
+ compile:esm: tsc -b ./config/tsconfig.build.json
+ compile:cjs: tsc -b ./config/tsconfig.cjs.json
+ clean: run-p clean:*
+ clean:build: bux rmrf ./build
+ clean:esm: bux rmrf ./lib
+ clean:cjs: bux rmrf ./lib-cjs
+ clean:generated: bux rmrf ./src/generated
+ test: jest
+ test:coverage: jest --collect-coverage
+ test:all: npm run test:coverage
+ test:debug: node --inspect-brk ./node_modules/.bin/jest --runInBand --watch
+ test:watch: jest --watch
+ lint: eslint . --max-warnings 0
+ watch: run-p "compile:* -- --watch --preserveWatchOutput"
+ workspace:build: npm run build
+ workspace:build:clean: npm run build:clean
+ workspace:build:desktop: npm run build
+ workspace:build:lib: npm run build
+ workspace:build:web: npm run build
+ workspace:build:package: npm run build:clean
+ workspace:build:prod: npm run build:clean
+ workspace:build:test: npm run build:test
+ workspace:build-translations: npm run build-translations
+ workspace:clean: npm run clean
+ workspace:launch:desktop: npm run watch
+ workspace:launch:web: npm run watch
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint -- --fix
+ workspace:start:desktop: npm run watch
+ workspace:start:web: npm run watch
+ workspace:test: npm run test
+ workspace:test:all: npm run test:all
+ workspace:test:desktop: npm run test
+ workspace:test:lib: npm run test
+ workspace:test:lib:all: npm run test:all
+ workspace:test:web: npm run test
+ workspace:test:web:all: npm run test:all
+ workspace:watch:lib: npm run watch
+
+C:\Users\m4x\BatchExplorer\packages\bonito-ui\package.json
+ build: npm run build-translations && npm run compile
+ build:clean: run-s clean build
+ build:test: run-s build test
+ bux: bux
+ build-translations: bux build-translations --src src --dest i18n --outputPath resources/i18n/json --packageName bonito.ui
+ compile: run-p compile:*
+ compile:esm: tsc -b ./config/tsconfig.build.json
+ compile:cjs: tsc -b ./config/tsconfig.cjs.json
+ clean: run-p clean:*
+ clean:build: bux rmrf ./build
+ clean:esm: bux rmrf ./lib
+ clean:cjs: bux rmrf ./lib-cjs
+ test: jest
+ test:coverage: jest --collect-coverage
+ test:all: cross-env BE_ENABLE_A11Y_TESTING=true npm run test:coverage
+ test:debug: node --inspect-brk ./node_modules/.bin/jest --runInBand --watch
+ test:watch: jest --watch
+ test:a11y: cross-env BE_ENABLE_A11Y_TESTING=true npm run test
+ test:a11y:watch: cross-env BE_ENABLE_A11Y_TESTING=true npm run test:watch
+ lint: eslint . --max-warnings 0
+ watch: run-p "compile:* -- --watch --preserveWatchOutput"
+ workspace:build: npm run build
+ workspace:build:clean: npm run build:clean
+ workspace:build:desktop: npm run build
+ workspace:build:lib: npm run build
+ workspace:build:web: npm run build
+ workspace:build:package: npm run build:clean
+ workspace:build:prod: npm run build:clean
+ workspace:build:test: npm run build:test
+ workspace:build-translations: npm run build-translations
+ workspace:clean: npm run clean
+ workspace:launch:desktop: npm run watch
+ workspace:launch:web: npm run watch
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint -- --fix
+ workspace:start:desktop: npm run watch
+ workspace:start:web: npm run watch
+ workspace:test: npm run test
+ workspace:test:all: npm run test:all
+ workspace:test:desktop: npm run test
+ workspace:test:lib: npm run test
+ workspace:test:lib:all: npm run test:all
+ workspace:test:web: npm run test
+ workspace:test:web:all: npm run test:all
+ workspace:watch:lib: npm run watch
+
+C:\Users\m4x\BatchExplorer\packages\playground\package.json
+ build: npm run build-translations && npm run compile
+ build:clean: run-s clean build
+ build:test: run-s build test
+ bux: bux
+ build-translations: bux build-translations --src src --dest i18n --outputPath resources/i18n/json --packageName lib.playground
+ compile: run-p compile:*
+ compile:esm: tsc -b ./config/tsconfig.build.json
+ compile:cjs: tsc -b ./config/tsconfig.cjs.json
+ clean: run-p clean:*
+ clean:build: bux rmrf ./build
+ clean:esm: bux rmrf ./lib
+ clean:cjs: bux rmrf ./lib-cjs
+ clean:generated: bux rmrf ./src/ui-playground/generated
+ test: jest
+ test:coverage: jest --collect-coverage
+ test:all: cross-env BE_ENABLE_A11Y_TESTING=true npm run test:coverage
+ test:debug: node --inspect-brk ./node_modules/.bin/jest --runInBand --watch
+ test:watch: jest --watch
+ lint: eslint . --max-warnings 0
+ watch: run-p "compile:* -- --watch --preserveWatchOutput"
+ workspace:watch:lib: npm run watch
+ workspace:build: npm run build
+ workspace:build:clean: npm run build:clean
+ workspace:build:web: npm run build
+ workspace:build:lib: npm run build
+ workspace:build:package: npm run build:clean
+ workspace:build:prod: npm run build:clean
+ workspace:build:test: npm run build:test
+ workspace:build-translations: npm run build-translations
+ workspace:clean: npm run clean
+ workspace:launch:desktop: npm run watch
+ workspace:launch:web: npm run watch
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint --fix
+ workspace:start:desktop: npm run watch
+ workspace:start:web: npm run watch
+ workspace:test: npm run test
+ workspace:test:all: npm run test:all
+ workspace:test:desktop: npm run test
+ workspace:test:web: npm run test
+ workspace:test:web:all: npm run test:all
+
+C:\Users\m4x\BatchExplorer\packages\react\package.json
+ build: npm run build-translations && npm run compile
+ build:clean: run-s clean build
+ build:test: run-s build test
+ bux: bux
+ build-translations: bux build-translations --src src --dest i18n --outputPath resources/i18n/json --packageName lib.react
+ compile: run-p compile:*
+ compile:esm: tsc -b ./config/tsconfig.build.json
+ compile:cjs: tsc -b ./config/tsconfig.cjs.json
+ clean: run-p clean:*
+ clean:build: bux rmrf ./build
+ clean:esm: bux rmrf ./lib
+ clean:cjs: bux rmrf ./lib-cjs
+ clean:generated: bux rmrf ./src/ui-react/generated
+ test: jest
+ test:coverage: jest --collect-coverage
+ test:all: cross-env BE_ENABLE_A11Y_TESTING=true npm run test:coverage
+ test:debug: node --inspect-brk ./node_modules/.bin/jest --runInBand --watch
+ test:watch: jest --watch
+ test:a11y: cross-env BE_ENABLE_A11Y_TESTING=true npm run test
+ test:a11y:watch: cross-env BE_ENABLE_A11Y_TESTING=true npm run test:watch
+ lint: eslint . --max-warnings 0
+ watch: run-p "compile:* -- --watch --preserveWatchOutput"
+ workspace:build: npm run build
+ workspace:build:clean: npm run build:clean
+ workspace:build:desktop: npm run build
+ workspace:build:lib: npm run build
+ workspace:build:web: npm run build
+ workspace:build:package: npm run build:clean
+ workspace:build:prod: npm run build:clean
+ workspace:build:test: npm run build:test
+ workspace:build-translations: npm run build-translations
+ workspace:clean: npm run clean
+ workspace:launch:desktop: npm run watch
+ workspace:launch:web: npm run watch
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint -- --fix
+ workspace:start:desktop: npm run watch
+ workspace:start:web: npm run watch
+ workspace:test: npm run test
+ workspace:test:all: npm run test:all
+ workspace:test:desktop: npm run test
+ workspace:test:lib: npm run test
+ workspace:test:lib:all: npm run test:all
+ workspace:test:web: npm run test
+ workspace:test:web:all: npm run test:all
+ workspace:watch:lib: npm run watch
+
+C:\Users\m4x\BatchExplorer\packages\service\package.json
+ build: npm run build-translations && npm run compile
+ build:clean: run-s clean build
+ build:test: run-s build test
+ build-translations: bux build-translations --src src --dest i18n --outputPath resources/i18n/json --packageName lib.service
+ compile: run-p compile:*
+ compile:esm: tsc -b ./config/tsconfig.build.json
+ compile:cjs: tsc -b ./config/tsconfig.cjs.json
+ clean: run-p clean:*
+ clean:build: bux rmrf ./build
+ clean:esm: bux rmrf ./lib
+ clean:cjs: bux rmrf ./lib-cjs
+ generate:arm-client: autorest --typescript swagger/README.md
+ generate:client: pwsh -ExecutionPolicy Bypass -File ./generate-client.ps1
+ test: jest
+ test:coverage: jest --collect-coverage
+ test:all: npm run test:coverage
+ test:debug: node --inspect-brk ./node_modules/.bin/jest --runInBand --watch
+ test:watch: jest --watch
+ lint: eslint . --max-warnings 0
+ watch: run-p "compile:* -- --watch --preserveWatchOutput"
+ workspace:build: npm run build
+ workspace:build:clean: npm run build:clean
+ workspace:build:desktop: npm run build
+ workspace:build:lib: npm run build
+ workspace:build:web: npm run build
+ workspace:build:package: npm run build:clean
+ workspace:build:prod: npm run build:clean
+ workspace:build:test: npm run build:test
+ workspace:build-translations: npm run build-translations
+ workspace:clean: npm run clean
+ workspace:launch:desktop: npm run watch
+ workspace:launch:web: npm run watch
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint --fix
+ workspace:start:desktop: npm run watch
+ workspace:start:web: npm run watch
+ workspace:test: npm run test
+ workspace:test:all: npm run test:all
+ workspace:test:desktop: npm run test
+ workspace:test:lib: npm run test
+ workspace:test:lib:all: npm run test:all
+ workspace:test:web: npm run test
+ workspace:test:web:all: npm run test:all
+ workspace:watch:lib: npm run watch
+
+C:\Users\m4x\BatchExplorer\util\bux\package.json
+ build: tsc -b ./config/tsconfig.build.json && node ./bin/bux chmodx ./bin/bux
+ bux: npm run build && node ./bin/bux
+ clean: rimraf ./lib && rimraf ./build
+ lint: eslint . --max-warnings 0
+ lint:fix: npm run lint --fix
+ build:clean: run-s clean build
+ prepack: npm run build:clean
+ test: jest
+ test:watch: npm test -- --watch
+ workspace:clean: npm run clean
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint:fix
+ workspace:test:all: npm run build:clean && npm test
+
+C:\Users\m4x\BatchExplorer\web\package.json
+ build: run-s compile bundle:dev
+ build:analyze: run-s clean compile bundle:analyze
+ build:clean: run-s clean compile bundle:dev
+ build:prod: run-s clean compile bundle:prod
+ build:test: run-s build test
+ build-translations: bux build-translations --src src --dest i18n --outputPath ./resources/i18n
+ bundle: npm run bundle:dev
+ bundle:analyze: webpack --env analyze
+ bundle:dev: webpack --env dev
+ bundle:prod: webpack
+ bux: bux
+ compile: npm run build-translations && tsc -b ./config/tsconfig.build.json
+ clean: run-p clean:*
+ clean:build: bux rmrf ./build
+ clean:esm: bux rmrf ./lib
+ clean:umd: bux rmrf ./lib-umd
+ clean:generated: bux rmrf ./src/generated
+ test: jest
+ test:coverage: jest --collect-coverage
+ test:all: cross-env BE_ENABLE_A11Y_TESTING=true npm run test:coverage
+ test:debug: node --inspect-brk ./node_modules/.bin/jest --runInBand --watch
+ test:watch: jest --watch
+ lint: eslint . --max-warnings 0
+ server: webpack-dev-server
+ start: npm run start:dev
+ start:dev: npm run clean && npm run build-translations && npm run server -- --env dev
+ start:prod: npm run clean && npm run server
+ watch: npm run compile -- --env watch
+ workspace:build: npm run build
+ workspace:build:clean: npm run build:clean
+ workspace:build:web: npm run build
+ workspace:build:package: npm run build:prod
+ workspace:build:prod: npm run build:prod
+ workspace:build:test: npm run build:test
+ workspace:build-translations: npm run build-translations
+ workspace:clean: npm run clean
+ workspace:launch:web: npm run start -- --env launch
+ workspace:lint: npm run lint
+ workspace:lint:fix: npm run lint -- --fix
+ workspace:start:web: npm run start
+ workspace:test: npm run test
+ workspace:test:all: npm run test:all
+ workspace:test:web: npm run test
+ workspace:test:web:all: npm run test:all
+
diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml
new file mode 100755
index 0000000000..91ccfcb87f
--- /dev/null
+++ b/.codex/environments/environment.toml
@@ -0,0 +1,49 @@
+# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
+version = 1
+name = "BatchExplorer"
+
+[setup]
+script = ""
+
+[[actions]]
+name = "Run"
+icon = "run"
+command = '''
+node -v # must be 18.x
+npm -v
+pwsh -v # PowerShell 7+
+Install Visual Studio Build Tools (for node-gyp) if missing.
+
+One-time Windows path setup (Admin PowerShell)
+New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
+git config --global core.longpaths true
+Install + bootstrap
+npm install --legacy-peer-deps
+npm run dev-setup
+npm run build
+Run the full desktop dev app
+npm run launch:desktop
+This runs watchers + dev server + Electron.
+
+Other run modes
+npm run start:web # web UI only (http://localhost:9000)
+npm run start:desktop # everything except Electron shell
+npm run launch:dev-electron
+If install fails with Cannot find module '@testing-library/dom'
+$src = Join-Path $PWD "node_modules\@testing-library\react\node_modules\@testing-library\dom"
+$dst = Join-Path $PWD "node_modules\@testing-library\dom"
+if (!(Test-Path $dst)) { New-Item -ItemType Junction -Path $dst -Target $src }
+Your current 409/400 API errors are Azure account configuration issues, not a local run issue:
+
+AccountNotEnabledForAutoStorage: Batch account needs linked auto-storage.
+AccountCostDisabled: cost endpoint is blocked/disabled for that account
+'''
+
+[[actions]]
+name = "T"
+icon = "tool"
+command = '''
+npm run start:web # web UI only (http://localhost:9000)
+npm run start:desktop # everything except Electron shell
+npm run launch:dev-electron
+'''
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..1878404ef1
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,44 @@
+# AGENTS.md
+
+## Cursor Cloud specific instructions
+
+### Overview
+
+Azure Batch Explorer is a Lerna-based monorepo for managing Azure Batch accounts. It ships as both an Electron desktop app and an experimental web UI. The codebase has 10 packages under `desktop/`, `packages/*`, `util/*`, and `web/`.
+
+### Node.js version
+
+This project requires **Node.js 18 LTS** (`nvm use 18`). Node 22+ causes `@testing-library/dom` hoisting issues that break the build. The VM snapshot should already have Node 18 set as the default.
+
+### Post-install setup caveat
+
+After `npm install`, `@testing-library/dom` (a transitive dependency of `@testing-library/react`) may not be hoisted to the root `node_modules/`. If the build fails with `Cannot find module '@testing-library/dom'`, fix it by symlinking:
+
+```sh
+ln -sf /workspace/node_modules/@testing-library/react/node_modules/@testing-library/dom /workspace/node_modules/@testing-library/dom
+```
+
+### Key commands
+
+All commands are run from the repo root (`/workspace`).
+
+| Task | Command |
+|------|---------|
+| Install dependencies | `npm install --legacy-peer-deps` |
+| Install bux CLI | `npm run dev-setup` |
+| Build all packages | `npm run build` |
+| Lint (Prettier + ESLint + Markdownlint + Stylelint) | `npm run lint` |
+| Test library packages | `npm run test:lib` |
+| Test web package | `npm run test:web` |
+| Start web dev server (port 9000) | `npm run start:web` |
+| Start desktop dev mode | `npm run launch:desktop` |
+
+See `docs/setup.md` for full setup instructions and `docs/testing.md` for testing details.
+
+### Desktop app
+
+The desktop app (`desktop/`) uses Electron + Angular 12 + Karma/Jasmine for tests. Running `npm run launch:desktop` requires a display (Electron GUI). Desktop tests (`npm run test:desktop`) also require a display since Karma launches Electron. The web UI and library packages can be built and tested headlessly.
+
+### Web app
+
+The web UI (`web/`) runs via webpack-dev-server on **port 9000**. Start it with `npm run start:web` which also watches all library packages. The web app uses fake/mock data in standalone mode and does not require Azure credentials to render.
diff --git a/desktop/src/app/components/pool/home/pool-home.component.ts b/desktop/src/app/components/pool/home/pool-home.component.ts
index 38f2114b1e..5238e19d14 100644
--- a/desktop/src/app/components/pool/home/pool-home.component.ts
+++ b/desktop/src/app/components/pool/home/pool-home.component.ts
@@ -1,23 +1,66 @@
-import { Component } from "@angular/core";
-import { autobind } from "@batch-flask/core";
+import { Component, OnDestroy } from "@angular/core";
+import { UserConfigurationService, autobind } from "@batch-flask/core";
+import { DialogService } from "@batch-flask/ui/dialogs";
import { SidebarManager } from "@batch-flask/ui/sidebar";
+import { BEUserConfiguration, DEFAULT_BE_USER_CONFIGURATION } from "common";
+import { Subscription, from } from "rxjs";
+import { finalize } from "rxjs/operators";
import { PoolCreateBasicDialogComponent } from "../action";
@Component({
selector: "bl-pool-home",
templateUrl: "pool-home.html",
})
-export class PoolHomeComponent {
+export class PoolHomeComponent implements OnDestroy {
+ public config: BEUserConfiguration = DEFAULT_BE_USER_CONFIGURATION;
+ public isRunning = false;
+ private _configSub: Subscription;
+
+ private _multiRegionPoolBootstrapService = {
+ run: () => new Promise((resolve) => {
+ setTimeout(resolve, 150);
+ }),
+ };
public static breadcrumb() {
return { name: "Pools" };
}
constructor(
- private sidebarManager: SidebarManager) {
+ private sidebarManager: SidebarManager,
+ private dialogService: DialogService,
+ private userConfigService: UserConfigurationService) {
+ this._configSub = this.userConfigService.config.subscribe((config) => {
+ this.config = config;
+ });
}
@autobind()
public addPool() {
this.sidebarManager.open("add-pool", PoolCreateBasicDialogComponent);
}
+
+ @autobind()
+ public startMultiRegionPoolBootstrap() {
+ if (this.isRunning) {
+ return;
+ }
+
+ this.dialogService.confirm("Start multi-region pool bootstrap?", {
+ description: "This starts the multi-region pool bootstrap flow.",
+ yes: () => {
+ this.isRunning = true;
+ return from(this._multiRegionPoolBootstrapService.run()).pipe(
+ finalize(() => {
+ this.isRunning = false;
+ }),
+ );
+ },
+ });
+ }
+
+ public ngOnDestroy() {
+ if (this._configSub) {
+ this._configSub.unsubscribe();
+ }
+ }
}
diff --git a/desktop/src/app/components/pool/home/pool-home.html b/desktop/src/app/components/pool/home/pool-home.html
index 370f290db2..a7c8f85990 100644
--- a/desktop/src/app/components/pool/home/pool-home.html
+++ b/desktop/src/app/components/pool/home/pool-home.html
@@ -6,6 +6,15 @@
+
+
diff --git a/desktop/src/app/services/azure-batch/core/batch-http.service.ts b/desktop/src/app/services/azure-batch/core/batch-http.service.ts
index c9a5fff560..d38c78e9cd 100644
--- a/desktop/src/app/services/azure-batch/core/batch-http.service.ts
+++ b/desktop/src/app/services/azure-batch/core/batch-http.service.ts
@@ -33,43 +33,52 @@ export class AzureBatchHttpService extends HttpService {
options = this._addApiVersion(uri, options);
return this.accountService.currentAccount.pipe(
take(1),
- flatMap((account) => {
- const url = this._computeUrl(uri, account);
- let obs;
- if (account instanceof ArmBatchAccount) {
- obs = this._setupRequestForArm(account, options);
- } else if (account instanceof LocalBatchAccount) {
- obs = this._setupRequestForSharedKey(account, method, url, options);
- } else {
- throw new InvalidAccountError(`Invalid account type ${account}`);
- }
- return obs.pipe(
- flatMap((options) => {
- return super.request(
- method,
- url,
- options).pipe(
- retryWhen(attempts => this.retryWhen(attempts)),
- catchError((error) => {
- if (error.status === 0) {
- return throwError(new ServerError({
- status: error.status,
- statusText: error.statusText,
- message: error.message,
- code: error.name,
- }));
- }
- const err = ServerError.fromBatchHttp(error);
- return throwError(err);
- }),
- );
- }),
- );
- }),
+ flatMap((account) => this._requestWithAccount(account, method, uri, options)),
+ shareReplay(1),
+ );
+ }
+
+ public requestForAccount(account: BatchAccount, method: any, uri?: any, options?: any): Observable {
+ options = this._addApiVersion(uri, options);
+ return this._requestWithAccount(account, method, uri, options).pipe(
shareReplay(1),
);
}
+ private _requestWithAccount(account: BatchAccount, method: any, uri: any, options: any): Observable {
+ const url = this._computeUrl(uri, account);
+ let obs;
+ if (account instanceof ArmBatchAccount) {
+ obs = this._setupRequestForArm(account, options);
+ } else if (account instanceof LocalBatchAccount) {
+ obs = this._setupRequestForSharedKey(account, method, url, options);
+ } else {
+ throw new InvalidAccountError(`Invalid account type ${account}`);
+ }
+ return obs.pipe(
+ flatMap((options) => {
+ return super.request(
+ method,
+ url,
+ options).pipe(
+ retryWhen(attempts => this.retryWhen(attempts)),
+ catchError((error) => {
+ if (error.status === 0) {
+ return throwError(new ServerError({
+ status: error.status,
+ statusText: error.statusText,
+ message: error.message,
+ code: error.name,
+ }));
+ }
+ const err = ServerError.fromBatchHttp(error);
+ return throwError(err);
+ }),
+ );
+ }),
+ );
+ }
+
private _setupRequestForArm(account: ArmBatchAccount, options) {
const tenantId = account.subscription.tenantId;
return this.auth.accessTokenData(tenantId, "batch").pipe(
diff --git a/desktop/src/app/services/index.ts b/desktop/src/app/services/index.ts
index 7f0fea8e69..21cc124c56 100644
--- a/desktop/src/app/services/index.ts
+++ b/desktop/src/app/services/index.ts
@@ -17,6 +17,7 @@ export * from "./http-upload-service";
export * from "./monitoring";
export * from "./navigator.service";
export * from "./pinned-entity";
+export * from "./pool-bootstrap";
export * from "./file-group.service";
export * from "./pricing.service";
export * from "./quota.service";
diff --git a/desktop/src/app/services/pool-bootstrap/index.ts b/desktop/src/app/services/pool-bootstrap/index.ts
new file mode 100755
index 0000000000..f4ebf59c2d
--- /dev/null
+++ b/desktop/src/app/services/pool-bootstrap/index.ts
@@ -0,0 +1,2 @@
+export * from "./multi-region-pool-bootstrap.service";
+export * from "./types";
diff --git a/desktop/src/app/services/pool-bootstrap/multi-region-pool-bootstrap.service.ts b/desktop/src/app/services/pool-bootstrap/multi-region-pool-bootstrap.service.ts
new file mode 100755
index 0000000000..d45689d2c2
--- /dev/null
+++ b/desktop/src/app/services/pool-bootstrap/multi-region-pool-bootstrap.service.ts
@@ -0,0 +1,188 @@
+import { Injectable } from "@angular/core";
+import { BatchAccount, ImageInformation, ImageInformationAttributes, VerificationType } from "app/models";
+import { PoolCreateDto } from "app/models/dtos";
+import { ImageReferenceDto, VirtualMachineConfigurationDto } from "app/models/dtos/virtual-machine-configuration.dto";
+import { Observable, empty } from "rxjs";
+import { expand, map, reduce } from "rxjs/operators";
+import { AzureBatchHttpService, BatchListResponse } from "../azure-batch/core";
+
+export interface MultiRegionPoolBootstrapConfig {
+ vmSize: string;
+}
+
+@Injectable({ providedIn: "root" })
+export class MultiRegionPoolBootstrapService {
+ private static readonly nearEolDaysThreshold = 365;
+ private static readonly selectUbuntuError = "unspecified: cannot select UbuntuLTS from supportedimages";
+
+ constructor(private readonly http: AzureBatchHttpService) {
+ }
+
+ public generatePoolId(location: string): string {
+ const normalizedLocation = this._normalizeLocation(location);
+ const timestamp = this._formatTimestamp(new Date());
+ const randomSuffix = this._randomSuffix(4);
+ return `bootstrap-${normalizedLocation}-${timestamp}-${randomSuffix}`;
+ }
+
+ public listSupportedImages(account: BatchAccount): Observable {
+ return this.http.requestForAccount(account, "GET", "/supportedimages").pipe(
+ expand((response: BatchListResponse) => {
+ return response["odata.nextLink"]
+ ? this.http.requestForAccount(account, "GET", response["odata.nextLink"])
+ : empty();
+ }),
+ reduce((allImages, response: BatchListResponse) => {
+ return allImages.concat((response && response.value) || []);
+ }, [] as ImageInformationAttributes[]),
+ map(images => images.map(x => new ImageInformation(x))),
+ );
+ }
+
+ public selectUbuntuLtsImage(images: ImageInformation[]): ImageInformation {
+ const ubuntuLtsImages = (images || []).filter(image => this._isCanonicalUbuntuLtsLinuxImage(image));
+ if (ubuntuLtsImages.length === 0) {
+ throw new Error(MultiRegionPoolBootstrapService.selectUbuntuError);
+ }
+
+ const verifiedCandidates = ubuntuLtsImages.filter(image => this._isVerified(image));
+ const verificationPreferred = verifiedCandidates.length > 0 ? verifiedCandidates : ubuntuLtsImages;
+
+ const nonNearEol = verificationPreferred.filter(image => !this._isNearEol(image));
+ const eolPreferred = nonNearEol.length > 0 ? nonNearEol : verificationPreferred;
+
+ const selectedImage = [...eolPreferred].sort((a, b) => this._compareUbuntuCandidates(a, b))[0];
+ if (!selectedImage) {
+ throw new Error(MultiRegionPoolBootstrapService.selectUbuntuError);
+ }
+ return selectedImage;
+ }
+
+ public buildBootstrapPoolCreateDto(
+ config: MultiRegionPoolBootstrapConfig,
+ poolId: string,
+ selectedImage: ImageInformation
+ ): PoolCreateDto {
+ const commandLine = selectedImage?.osType?.toLowerCase() === "windows"
+ ? "cmd /c \"echo bootstrap\""
+ : "/bin/sh -c \"echo bootstrap\"";
+
+ return new PoolCreateDto({
+ id: poolId,
+ vmSize: config.vmSize,
+ targetDedicatedNodes: 0,
+ enableAutoScale: false,
+ virtualMachineConfiguration: new VirtualMachineConfigurationDto({
+ nodeAgentSKUId: selectedImage.nodeAgentSKUId,
+ imageReference: new ImageReferenceDto({
+ publisher: selectedImage.imageReference?.publisher,
+ offer: selectedImage.imageReference?.offer,
+ sku: selectedImage.imageReference?.sku,
+ version: selectedImage.imageReference?.version || "latest",
+ virtualMachineImageId: selectedImage.imageReference?.virtualMachineImageId,
+ }),
+ }),
+ startTask: {
+ commandLine,
+ },
+ });
+ }
+
+ private _normalizeLocation(location: string): string {
+ return ((location || "unspecified")
+ .toLowerCase()
+ .replace(/\s+/g, "")
+ .replace(/[^a-z0-9-]/g, "")) || "unspecified";
+ }
+
+ private _formatTimestamp(date: Date): string {
+ const year = date.getUTCFullYear();
+ const month = this._pad2(date.getUTCMonth() + 1);
+ const day = this._pad2(date.getUTCDate());
+ const hour = this._pad2(date.getUTCHours());
+ const minute = this._pad2(date.getUTCMinutes());
+ return `${year}${month}${day}-${hour}${minute}`;
+ }
+
+ private _randomSuffix(length: number): string {
+ let output = "";
+ while (output.length < length) {
+ output += Math.random().toString(36).slice(2);
+ }
+ return output.slice(0, length);
+ }
+
+ private _pad2(value: number): string {
+ return `${value}`.padStart(2, "0");
+ }
+
+ private _isCanonicalUbuntuLtsLinuxImage(image: ImageInformation): boolean {
+ const osType = (image && image.osType || "").toLowerCase();
+ const publisher = (image && image.imageReference && image.imageReference.publisher || "").toLowerCase();
+ const offer = (image && image.imageReference && image.imageReference.offer || "").toLowerCase();
+ const sku = (image && image.imageReference && image.imageReference.sku || "").toLowerCase();
+
+ const isUbuntu = offer.includes("ubuntu") || sku.includes("ubuntu");
+ const isLts = offer.includes("lts") || sku.includes("lts");
+
+ return osType === "linux"
+ && publisher === "canonical"
+ && isUbuntu
+ && isLts;
+ }
+
+ private _isVerified(image: ImageInformation): boolean {
+ return (image && image.verificationType || "").toLowerCase() === VerificationType.Verified;
+ }
+
+ private _isNearEol(image: ImageInformation): boolean {
+ if (!(image && image.batchSupportEndOfLife)) {
+ return false;
+ }
+
+ const eolDate = new Date(image.batchSupportEndOfLife);
+ if (Number.isNaN(eolDate.getTime())) {
+ return false;
+ }
+
+ const diffInDays = (eolDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24);
+ return diffInDays <= MultiRegionPoolBootstrapService.nearEolDaysThreshold;
+ }
+
+ private _compareUbuntuCandidates(a: ImageInformation, b: ImageInformation): number {
+ const versionDiff = this._ubuntuVersionScore(b) - this._ubuntuVersionScore(a);
+ if (versionDiff !== 0) {
+ return versionDiff;
+ }
+
+ const eolDiff = this._eolTimestampForSort(b) - this._eolTimestampForSort(a);
+ if (eolDiff !== 0) {
+ return eolDiff;
+ }
+
+ const aAgent = (a && a.nodeAgentSKUId) || "";
+ const bAgent = (b && b.nodeAgentSKUId) || "";
+ return aAgent.localeCompare(bAgent);
+ }
+
+ private _ubuntuVersionScore(image: ImageInformation): number {
+ const text = [
+ image && image.imageReference && image.imageReference.sku,
+ image && image.imageReference && image.imageReference.offer,
+ image && image.nodeAgentSKUId,
+ ].join(" ").toLowerCase();
+ const match = text.match(/(\d{2})[._-](\d{2})/);
+ if (!match) {
+ return -1;
+ }
+ return Number(match[1]) * 100 + Number(match[2]);
+ }
+
+ private _eolTimestampForSort(image: ImageInformation): number {
+ if (!(image && image.batchSupportEndOfLife)) {
+ return Number.MAX_SAFE_INTEGER;
+ }
+ const timestamp = new Date(image.batchSupportEndOfLife).getTime();
+ return Number.isNaN(timestamp) ? Number.MAX_SAFE_INTEGER : timestamp;
+ }
+}
diff --git a/desktop/src/app/services/pool-bootstrap/types.ts b/desktop/src/app/services/pool-bootstrap/types.ts
new file mode 100755
index 0000000000..dfbe4850e6
--- /dev/null
+++ b/desktop/src/app/services/pool-bootstrap/types.ts
@@ -0,0 +1,22 @@
+export type PoolBootstrapStatus = "pending" | "inProgress" | "stopped" | "completed" | "failed";
+
+export interface BootstrapSummaryBase {
+ accountId: string;
+ location: string;
+ endpoint: string;
+ status: PoolBootstrapStatus;
+ stopReason: string | null;
+ lastSuccessfulTarget: string | null;
+ errors: string[];
+}
+
+export interface PerAccountSummary extends BootstrapSummaryBase {
+ activitySteps: string[];
+}
+
+export interface GlobalSummary extends BootstrapSummaryBase {
+ startedAt: string;
+ completedAt: string | null;
+ activitySteps: string[];
+ accounts: PerAccountSummary[];
+}
diff --git a/desktop/src/app/services/quota.service.ts b/desktop/src/app/services/quota.service.ts
index 63a40a1bd2..1a7c9e7c3c 100644
--- a/desktop/src/app/services/quota.service.ts
+++ b/desktop/src/app/services/quota.service.ts
@@ -1,9 +1,9 @@
import { Injectable, OnDestroy } from "@angular/core";
import { FilterBuilder } from "@batch-flask/core";
-import { ArmBatchAccount, JobState, Pool } from "app/models";
+import { ArmBatchAccount, BatchApplication, JobState, Pool } from "app/models";
import { List } from "immutable";
-import { BehaviorSubject, Observable, Subject, combineLatest, forkJoin, of } from "rxjs";
-import { filter, map, publishReplay, refCount, switchMap, takeUntil } from "rxjs/operators";
+import { BehaviorSubject, Observable, Subject, combineLatest, forkJoin, of, throwError } from "rxjs";
+import { catchError, filter, map, publishReplay, refCount, switchMap, takeUntil } from "rxjs/operators";
import { BatchApplicationService } from "./azure-batch/batch-application/batch-application.service";
import { JobService } from "./azure-batch/job";
import { PoolService } from "./azure-batch/pool";
@@ -118,7 +118,17 @@ export class QuotaService implements OnDestroy {
public updateApplicationUsage() {
const obs = this.applicationService.listAll({
select: "id",
- });
+ }).pipe(
+ catchError((error) => {
+ if (this.applicationService.isAutoStorageError(error)) {
+ this._applicationUsage.next({
+ applications: 0,
+ });
+ return of(List());
+ }
+ return throwError(error);
+ }),
+ );
obs.subscribe((applications) => {
this._applicationUsage.next({
applications: applications.size,
diff --git a/desktop/src/common/be-user-configuration.model.ts b/desktop/src/common/be-user-configuration.model.ts
index af74b052cb..e1cc312e2a 100644
--- a/desktop/src/common/be-user-configuration.model.ts
+++ b/desktop/src/common/be-user-configuration.model.ts
@@ -8,6 +8,25 @@ export interface BEUserConfiguration extends BatchFlaskUserConfiguration {
externalBrowserAuth: boolean;
+ features: {
+ multiRegionPoolBootstrap: boolean,
+ };
+
+ multiRegionPoolBootstrap: {
+ scope: {
+ maxTargetPerAccount: number,
+ };
+ pool: {
+ vmSize: string,
+ };
+ execution: {
+ retryBackoffSeconds: number[],
+ };
+ policy: {
+ dedicatedOnly: boolean,
+ };
+ };
+
subscriptions: {
ignore: string[],
};
@@ -43,6 +62,23 @@ export const DEFAULT_BE_USER_CONFIGURATION: BEUserConfiguration = {
entityConfiguration: {
defaultView: EntityConfigurationView.Pretty,
},
+ features: {
+ multiRegionPoolBootstrap: false,
+ },
+ multiRegionPoolBootstrap: {
+ scope: {
+ maxTargetPerAccount: 20,
+ },
+ pool: {
+ vmSize: "Standard_D2s_v3",
+ },
+ execution: {
+ retryBackoffSeconds: [2, 4, 8, 16, 32],
+ },
+ policy: {
+ dedicatedOnly: true,
+ },
+ },
subscriptions: {
ignore: [],
},
diff --git a/package-lock.json b/package-lock.json
index 112fd2bfcb..51dc85b553 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2611,61 +2611,6 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@testing-library/dom": {
- "version": "9.3.1",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz",
- "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.1.3",
- "chalk": "^4.1.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "pretty-format": "^27.0.2"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@testing-library/dom/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@testing-library/dom/node_modules/pretty-format": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
- "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^17.0.1"
- },
- "engines": {
- "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
- }
- },
- "node_modules/@testing-library/dom/node_modules/react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "dev": true,
- "peer": true
- },
"node_modules/@testing-library/react": {
"version": "12.1.5",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz",
@@ -2866,12 +2811,14 @@
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
- "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
+ "dev": true
},
"node_modules/@types/react": {
"version": "17.0.59",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.59.tgz",
"integrity": "sha512-gSON5zWYIGyoBcycCE75E9+r6dCC2dHdsrVkOEiIYNU5+Q28HcBAuqvDuxHcCbMfHBHdeT5Tva/AFn3rnMKE4g==",
+ "dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -2882,6 +2829,7 @@
"version": "17.0.20",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.20.tgz",
"integrity": "sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA==",
+ "dev": true,
"dependencies": {
"@types/react": "^17"
}
@@ -2889,7 +2837,8 @@
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
- "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
+ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
+ "dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.33.0",
@@ -4356,7 +4305,8 @@
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
- "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+ "dev": true
},
"node_modules/dargs": {
"version": "7.0.0",
diff --git a/util/bux/package-lock.json b/util/bux/package-lock.json
index 15aab65b23..46503c5b73 100644
--- a/util/bux/package-lock.json
+++ b/util/bux/package-lock.json
@@ -1116,7 +1116,7 @@
"version": "20.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.4.tgz",
"integrity": "sha512-Y9vbIAoM31djQZrPYjpTLo0XlaSwOIsrlfE3LpulZeRblttsLQRFRlBAppW0LOxyT3ALj2M5vU1ucQQayQH3jA==",
- "devOptional": true
+ "dev": true
},
"node_modules/@types/prettier": {
"version": "2.7.3",
@@ -5541,20 +5541,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
diff --git a/util/common-config/package-lock.json b/util/common-config/package-lock.json
index 474f8844dd..4f2a59e5ee 100644
--- a/util/common-config/package-lock.json
+++ b/util/common-config/package-lock.json
@@ -3961,20 +3961,6 @@
"is-typedarray": "^1.0.0"
}
},
- "node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
- "dev": true,
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=4.2.0"
- }
- },
"node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
diff --git a/web/webpack.config.js b/web/webpack.config.js
index e24addf4ff..c4ad939858 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -13,7 +13,9 @@ const { EsbuildPlugin } = require("esbuild-loader");
const MODE_DEV = "development";
const MODE_PROD = "production";
-
+const ANALYZE_MODE = true;
+const WATCH_MODE = true;
+const LAUNCH_BROWSER = true;
/**
* Build a bundle which can be imported into a regular web page and used without
* any external dependencies.