mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-11-01 08:19:44 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed46ac0b40 | |||
| 6901f6f682 |
@@ -105,7 +105,7 @@ jobs:
|
||||
# the local install so the gate and the pre-commit run agree on the finding set.
|
||||
# Informational findings (template expansion of our own step outputs in pwsh) are
|
||||
# visible locally but do not fail the gate.
|
||||
- uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
|
||||
- uses: zizmorcore/zizmor-action@53290badfa369cb0bf7ad16342cb62c2fa47ac7b # v0.8.0
|
||||
with:
|
||||
version: 1.30.1
|
||||
min-severity: low
|
||||
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
Get-ChildItem $outDir | ForEach-Object { Write-Host "$($_.Name): $([math]::Round($_.Length / 1MB, 2)) MB" }
|
||||
|
||||
- name: Upload unsigned build output
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: agent-unsigned
|
||||
path: ${{ runner.temp }}/agent-unsigned
|
||||
@@ -293,7 +293,7 @@ jobs:
|
||||
run: ./.github/scripts/Get-Endpoints.ps1 -ExportToGitHubEnv
|
||||
|
||||
- name: Download unsigned build output
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: agent-unsigned
|
||||
path: ${{ runner.temp }}/agent-unsigned
|
||||
@@ -790,13 +790,13 @@ jobs:
|
||||
staging/agent/AutopilotMonitor.SummaryDialog.exe
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: AutopilotMonitor-Agent-v${{ needs.reserve.outputs.version }}
|
||||
path: ${{ steps.artifact.outputs.versioned_zip_release }}
|
||||
|
||||
- name: Upload version metadata
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: version-metadata
|
||||
path: ${{ env.MANIFEST_NAME }}
|
||||
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
Write-Host "$env:RUNNER_SCRIPT : Valid, timestamped, signer $($sig.SignerCertificate.Subject.Split(',')[0])"
|
||||
|
||||
- name: Upload signed loader
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: bootstrap-loader-signed
|
||||
path: ${{ env.RUNNER_SCRIPT }}
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download signed loader
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: bootstrap-loader-signed
|
||||
path: ${{ runner.temp }}/loader
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
Write-Host "Built $env:STABLE_MSI ($([math]::Round((Get-Item $env:STABLE_MSI).Length / 1KB)) KB)"
|
||||
|
||||
- name: Upload unsigned MSI
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: bootstrap-msi-unsigned
|
||||
path: ${{ env.STABLE_MSI }}
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download unsigned MSI
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: bootstrap-msi-unsigned
|
||||
path: ${{ runner.temp }}/msi
|
||||
@@ -310,7 +310,7 @@ jobs:
|
||||
"version=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Download unsigned MSI
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: bootstrap-msi-unsigned
|
||||
path: ${{ runner.temp }}/msi
|
||||
@@ -442,7 +442,7 @@ jobs:
|
||||
subject-path: ${{ env.STABLE_MSI }}
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: AutopilotMonitor-Bootstrap-v${{ steps.version.outputs.version }}
|
||||
path: ${{ env.STABLE_MSI }}
|
||||
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
@@ -245,7 +245,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
@@ -276,7 +276,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -123,6 +123,6 @@ jobs:
|
||||
run: dotnet build AutopilotMonitor.sln --configuration Release
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
# `git push` needs.
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
run: tar -czf "$RUNNER_TEMP/backend-publish.tgz" -C publish .
|
||||
|
||||
- name: Upload publish output
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: backend-publish
|
||||
path: ${{ runner.temp }}/backend-publish.tgz
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
run: ./.github/scripts/Get-Endpoints.ps1 -ExportToGitHubEnv
|
||||
|
||||
- name: Download publish output
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: backend-publish
|
||||
path: ${{ runner.temp }}/backend-artifact
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
id: docs
|
||||
run: echo "sha=$(git -C whats-new-docs rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
sha256sum "$archive"
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: portal-${{ github.sha }}
|
||||
path: ${{ steps.package.outputs.archive }}
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: portal-${{ github.sha }}
|
||||
path: ${{ runner.temp }}/portal
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
run: ./.github/scripts/Get-Endpoints.ps1 -ExportToGitHubEnv
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: portal-${{ github.sha }}
|
||||
path: ${{ runner.temp }}/portal
|
||||
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
# when someone wants to look at the file.
|
||||
- name: Upload signed scripts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: signed-scripts
|
||||
path: ${{ runner.temp }}/signed-scripts
|
||||
|
||||
@@ -47,6 +47,39 @@ function formatDuration(ms: number): string {
|
||||
return `${hours}h ${remainingMinutes}m`;
|
||||
}
|
||||
|
||||
interface DetectionBadge {
|
||||
label: string;
|
||||
/** Label below `sm`; null hides the pill there — it then appears in the row's Details only. */
|
||||
phoneLabel: string | null;
|
||||
tone: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
// IME's check right after the install, or after the restart the installer requested (D-392).
|
||||
// Any result other than Detected/NotDetected is IME's DetectedDifferentVersion.
|
||||
function postInstallDetectionBadge(result: string, afterReboot: boolean | undefined): DetectionBadge {
|
||||
const check = `Intune's detection check right after the ${afterReboot ? "restart the installer requested" : "installer finished"}`;
|
||||
if (result === "Detected") {
|
||||
// Only confirms what the check icon and the green row already say, so on a phone it yields
|
||||
// its room to the app name instead of squeezing it to a few letters.
|
||||
return { label: "Detected after install", phoneLabel: null, tone: "bg-green-100 text-green-700", title: `${check}: the app was found.` };
|
||||
}
|
||||
if (result === "NotDetected") {
|
||||
return {
|
||||
label: "Not detected after install",
|
||||
phoneLabel: "Not detected",
|
||||
tone: "bg-red-200 text-red-800",
|
||||
title: `${check}: the app was not found, so Intune counts the install as failed (0x87D1041C).`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: "Different version detected",
|
||||
phoneLabel: "Other version",
|
||||
tone: "bg-amber-200 text-amber-700",
|
||||
title: `${check}: a different version was found; Intune counts the app as installed.`,
|
||||
};
|
||||
}
|
||||
|
||||
export default function InstallProgress({ events, summaryStats, observedUntilMs = null, userPhaseBoundary = null }: InstallProgressProps) {
|
||||
// Legacy-agent guard: split off app events replayed from a previous enrollment's IME log
|
||||
// (newer agents suppress them at the source) so week-old installs never render as current.
|
||||
@@ -263,6 +296,10 @@ function InstallItemRow({ item }: { item: InstallItem }) {
|
||||
? liveElapsed.elapsedMs
|
||||
: null;
|
||||
|
||||
// A failed row already says "Detection failed", so its NotDetected verdict is not repeated.
|
||||
const detection = item.postInstallDetection && !(item.state === "Failed" && item.postInstallDetection === "NotDetected")
|
||||
? postInstallDetectionBadge(item.postInstallDetection, item.postInstallAfterReboot)
|
||||
: null;
|
||||
|
||||
// Session 080edee9 follow-up — only the genuine "no HRESULT available" timeout
|
||||
// case wears the hedged orange treatment. Detection-failures and install-failures
|
||||
@@ -343,29 +380,19 @@ function InstallItemRow({ item }: { item: InstallItem }) {
|
||||
Uninstall
|
||||
</span>
|
||||
)}
|
||||
{item.postInstallDetection && !(item.state === "Failed" && item.postInstallDetection === "NotDetected") && (
|
||||
// IME's check right after the install (D-392); a failed row already says "Detection failed".
|
||||
{detection && (
|
||||
// The pill never shrinks (nowrap) while the name does, so on a phone a long label
|
||||
// squeezed the app name to a few letters: short label there, or none at all.
|
||||
<span
|
||||
className={`text-xs px-2 py-0.5 rounded-full font-medium whitespace-nowrap ${
|
||||
item.postInstallDetection === "Detected"
|
||||
? "bg-green-100 text-green-700"
|
||||
: item.postInstallDetection === "NotDetected"
|
||||
? "bg-red-200 text-red-800"
|
||||
: "bg-amber-200 text-amber-700"
|
||||
}`}
|
||||
title={`Intune's detection check right after the ${item.postInstallAfterReboot ? "restart the installer requested" : "installer finished"}: ${
|
||||
item.postInstallDetection === "Detected"
|
||||
? "the app was found."
|
||||
: item.postInstallDetection === "NotDetected"
|
||||
? "the app was not found, so Intune counts the install as failed (0x87D1041C)."
|
||||
: "a different version was found; Intune counts the app as installed."
|
||||
}`}
|
||||
className={`text-xs px-2 py-0.5 rounded-full font-medium whitespace-nowrap ${detection.tone}${detection.phoneLabel == null ? " hidden sm:inline" : ""}`}
|
||||
title={detection.title}
|
||||
>
|
||||
{item.postInstallDetection === "Detected"
|
||||
? "Detected after install"
|
||||
: item.postInstallDetection === "NotDetected"
|
||||
? "Not detected after install"
|
||||
: "Different version detected"}
|
||||
{detection.phoneLabel == null ? detection.label : (
|
||||
<>
|
||||
<span className="sm:hidden">{detection.phoneLabel}</span>
|
||||
<span className="hidden sm:inline">{detection.label}</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{item.state === "Failed" && item.isLikelyStuck && (
|
||||
@@ -516,6 +543,15 @@ function InstallItemRow({ item }: { item: InstallItem }) {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Phones: the row header shows the detection result short or not at all, and touch has
|
||||
no hover tooltip — Details carries the full verdict. */}
|
||||
{showDetails && detection && (
|
||||
<div className="sm:hidden mt-3 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs">
|
||||
<span className={`px-2 py-0.5 rounded-full font-medium whitespace-nowrap ${detection.tone}`}>{detection.label}</span>
|
||||
<span className="text-gray-600">{detection.title}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Event details (expandable) */}
|
||||
{showDetails && item.eventData && (
|
||||
<div className="mt-3 p-2 bg-gray-900 rounded text-xs text-gray-100 font-mono overflow-x-auto">
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import InstallProgress from "../InstallProgress";
|
||||
import type { InstallEvent } from "@/lib/installProgress";
|
||||
|
||||
function evt(eventType: string, timestamp: string, data: Record<string, unknown>): InstallEvent {
|
||||
return { eventType, timestamp, data };
|
||||
}
|
||||
|
||||
function renderWithCheckAfterInstall(postInstallDetection: string): string {
|
||||
return renderToStaticMarkup(
|
||||
createElement(InstallProgress, {
|
||||
events: [
|
||||
evt("app_install_started", "2026-10-21T16:40:00Z", { appName: "Contoso VPN" }),
|
||||
evt("app_install_completed", "2026-10-21T16:42:39Z", { appName: "Contoso VPN", state: "Installed" }),
|
||||
evt("app_post_install_detection", "2026-10-21T16:42:41Z", { appName: "Contoso VPN", postInstallDetection, afterReboot: "false" }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("InstallProgress — detection pill on narrow screens", () => {
|
||||
it("hides the green confirmation below sm so the app name keeps its room", () => {
|
||||
const html = renderWithCheckAfterInstall("Detected");
|
||||
expect(html).toContain("bg-green-100 text-green-700 hidden sm:inline");
|
||||
expect(html).toContain(">Detected after install</span>");
|
||||
expect(html).not.toContain('class="sm:hidden"');
|
||||
});
|
||||
|
||||
it("keeps the red and amber verdicts on phones, with a short label", () => {
|
||||
const notDetected = renderWithCheckAfterInstall("NotDetected");
|
||||
expect(notDetected).toContain('<span class="sm:hidden">Not detected</span>');
|
||||
expect(notDetected).toContain('<span class="hidden sm:inline">Not detected after install</span>');
|
||||
|
||||
const otherVersion = renderWithCheckAfterInstall("DetectedDifferentVersion");
|
||||
expect(otherVersion).toContain('<span class="sm:hidden">Other version</span>');
|
||||
expect(otherVersion).toContain('<span class="hidden sm:inline">Different version detected</span>');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user