fix(manager): ship canonical NODE.DC favicon set
This commit is contained in:
@@ -4,6 +4,12 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#101114" />
|
<meta name="theme-color" content="#101114" />
|
||||||
|
<link rel="icon" type="image/svg+xml" sizes="any" href="/favicon/icon-adaptive.svg" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="/favicon/icon-192.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="512x512" href="/favicon/icon-512.png" />
|
||||||
|
<link rel="manifest" href="/favicon/manifest.webmanifest.json" />
|
||||||
<title>NODE.DC Device Core</title>
|
<title>NODE.DC Device Core</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<style>
|
||||||
|
.icon-fav-01 { fill: #333334; } /* Тёмный цвет для светлого таб-бара */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.icon-fav-01 { fill: #FEFEFE; } /* Светлый цвет для тёмного таб-бара */
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
.icon-fav-01 { fill: #000000; } /* Тёмный цвет для светлого таб-бара (для явности) */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<path class="icon-fav-01" d="M17.8738 15.9159L16.0002 18.9718L14.1267 15.9159H17.8738ZM23.6307 12.7864H8.36914L15.9999 25.2308L23.6307 12.7864Z"/>
|
||||||
|
<path class="icon-fav-01" d="M10.9793 19.4872L6.67269 11.5918H25.5344L21.2281 19.4872H25.0581L31.2614 8H0.738281L7.16244 19.4872H10.9793Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 852 B |
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"theme_color": "#eeeff4",
|
||||||
|
"background_color": "#eeeff4",
|
||||||
|
"display": "browser",
|
||||||
|
"scope": "/",
|
||||||
|
"start_url": "/",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icon-adaptive.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icon-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icon-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "apple-touch-icon.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "180x180"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "nodedc.device-plane.device-manager-release.v6",
|
||||||
|
"releaseId": "__PATCH_ID__",
|
||||||
|
"action": "upgrade",
|
||||||
|
"predecessor": {
|
||||||
|
"kind": "release",
|
||||||
|
"patchId": "device-manager-release-v5-20260822-034",
|
||||||
|
"artifactSha256": "acc1d2ae2cda66861054826928c25d01a2428e688cc8132a9c381831bf29ab5a"
|
||||||
|
},
|
||||||
|
"controlCorePredecessor": {
|
||||||
|
"patchId": "device-control-core-release-v2-20260821-030",
|
||||||
|
"artifactSha256": "8459521a662541a5a87cb0188991cdcfb51727427db8ec2a232ce4846bfc3454"
|
||||||
|
},
|
||||||
|
"edgeChannelPredecessor": {
|
||||||
|
"patchId": "device-edge-core-channel-upgrade-v4-20260812-023",
|
||||||
|
"artifactSha256": "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
||||||
|
},
|
||||||
|
"service": "device-manager",
|
||||||
|
"publicIngress": "reverse-proxy-only",
|
||||||
|
"deviceCoreManagementApi": "file-token-authenticated",
|
||||||
|
"launcherTrust": "file-token-scoped-to-device-core-handoff",
|
||||||
|
"edgeChannel": "preserve-active-v4-core-initiated-pinned-mtls",
|
||||||
|
"edgeChannelIdentity": "reuse-runner-managed-host-local-private-key-public-certificate-export",
|
||||||
|
"edgeChannelEgress": "preserve-dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-443-only",
|
||||||
|
"healthGate": "bounded-container-grace+core-contract+persistent-data",
|
||||||
|
"commandTransport": "typed-service-ping-v1",
|
||||||
|
"commandCatalog": "allowlisted-adapter-typed-commands-only",
|
||||||
|
"credentialBoundary": "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned",
|
||||||
|
"presentationPersistence": "runner-managed-host-data-bind",
|
||||||
|
"presentationDataHostPath": "/volume1/docker/nodedc-device-plane/data/device-manager",
|
||||||
|
"presentationDataContainerPath": "/var/lib/nodedc-device-manager",
|
||||||
|
"presentationDataOwnership": "uid-1000-gid-1000-mode-0750",
|
||||||
|
"presentationDataLifecycle": "preserve-across-manager-recreate-and-source-rollback",
|
||||||
|
"presentationPath": "/var/lib/nodedc-device-manager/device-manager-presentation.json",
|
||||||
|
"mediaRoot": "/var/lib/nodedc-device-manager/media",
|
||||||
|
"defaultAccentHex": "#f5f5f5",
|
||||||
|
"overviewLayout": "mission-core-landing-stage-v1",
|
||||||
|
"faviconSet": "nodedc-adaptive-v1",
|
||||||
|
"gelios": "untouched-legacy-only",
|
||||||
|
"rollback": "restore-preapply-snapshot-preserve-manager-data"
|
||||||
|
}
|
||||||
@@ -11,21 +11,24 @@ const platformRoot = resolve(scriptDir, "../..");
|
|||||||
const devicePlaneRoot = platformRoot;
|
const devicePlaneRoot = platformRoot;
|
||||||
const managerRoot = resolve(platformRoot, "apps/device-manager");
|
const managerRoot = resolve(platformRoot, "apps/device-manager");
|
||||||
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
|
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
|
||||||
const [patchId = "device-manager-release-v5-20260822-034", ...extra] = process.argv.slice(2);
|
const [patchId = "device-manager-release-v6-20260822-035", ...extra] = process.argv.slice(2);
|
||||||
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
|
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
|
||||||
|
|
||||||
const descriptorPath = patchId.startsWith("device-manager-release-v5-")
|
const descriptorPath = patchId.startsWith("device-manager-release-v6-")
|
||||||
? "deployment/device-manager-release-v5.json"
|
? "deployment/device-manager-release-v6.json"
|
||||||
: patchId.startsWith("device-manager-release-v4-")
|
: patchId.startsWith("device-manager-release-v5-")
|
||||||
? "deployment/device-manager-release-v4.json"
|
? "deployment/device-manager-release-v5.json"
|
||||||
: patchId.startsWith("device-manager-release-v3-")
|
: patchId.startsWith("device-manager-release-v4-")
|
||||||
? "deployment/device-manager-release-v3.json"
|
? "deployment/device-manager-release-v4.json"
|
||||||
: "deployment/device-manager-release-v1.json";
|
: patchId.startsWith("device-manager-release-v3-")
|
||||||
|
? "deployment/device-manager-release-v3.json"
|
||||||
|
: "deployment/device-manager-release-v1.json";
|
||||||
|
|
||||||
const isV3 = descriptorPath.endsWith("release-v3.json");
|
const isV3 = descriptorPath.endsWith("release-v3.json");
|
||||||
const isV4 = descriptorPath.endsWith("release-v4.json");
|
const isV4 = descriptorPath.endsWith("release-v4.json");
|
||||||
const isV5 = descriptorPath.endsWith("release-v5.json");
|
const isV5 = descriptorPath.endsWith("release-v5.json");
|
||||||
const isPersistent = isV4 || isV5;
|
const isV6 = descriptorPath.endsWith("release-v6.json");
|
||||||
|
const isPersistent = isV4 || isV5 || isV6;
|
||||||
const isManagerOnly = isV3 || isPersistent;
|
const isManagerOnly = isV3 || isPersistent;
|
||||||
const composeSource = resolve(devicePlaneRoot, "docker-compose.device-manager.yml");
|
const composeSource = resolve(devicePlaneRoot, "docker-compose.device-manager.yml");
|
||||||
const composeSourceSha256 = createHash("sha256").update(await readFile(composeSource)).digest("hex");
|
const composeSourceSha256 = createHash("sha256").update(await readFile(composeSource)).digest("hex");
|
||||||
@@ -164,7 +167,56 @@ try {
|
|||||||
: "restore-preapply-snapshot")
|
: "restore-preapply-snapshot")
|
||||||
);
|
);
|
||||||
if (commonContractInvalid) throw new Error("device_manager_activation_successor_contract_mismatch");
|
if (commonContractInvalid) throw new Error("device_manager_activation_successor_contract_mismatch");
|
||||||
if (descriptorPath.endsWith("release-v5.json")) {
|
if (descriptorPath.endsWith("release-v6.json")) {
|
||||||
|
if (
|
||||||
|
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v6"
|
||||||
|
|| descriptor.predecessor?.kind !== "release"
|
||||||
|
|| descriptor.predecessor?.patchId !== "device-manager-release-v5-20260822-034"
|
||||||
|
|| descriptor.predecessor?.artifactSha256 !== "acc1d2ae2cda66861054826928c25d01a2428e688cc8132a9c381831bf29ab5a"
|
||||||
|
|| descriptor.commandTransport !== "typed-service-ping-v1"
|
||||||
|
|| descriptor.commandCatalog !== "allowlisted-adapter-typed-commands-only"
|
||||||
|
|| descriptor.credentialBoundary !== "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned"
|
||||||
|
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v2-20260821-030"
|
||||||
|
|| descriptor.controlCorePredecessor?.artifactSha256 !== "8459521a662541a5a87cb0188991cdcfb51727427db8ec2a232ce4846bfc3454"
|
||||||
|
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|
||||||
|
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
||||||
|
|| descriptor.presentationPersistence !== "runner-managed-host-data-bind"
|
||||||
|
|| descriptor.presentationDataHostPath !== "/volume1/docker/nodedc-device-plane/data/device-manager"
|
||||||
|
|| descriptor.presentationDataContainerPath !== "/var/lib/nodedc-device-manager"
|
||||||
|
|| descriptor.presentationDataOwnership !== "uid-1000-gid-1000-mode-0750"
|
||||||
|
|| descriptor.presentationDataLifecycle !== "preserve-across-manager-recreate-and-source-rollback"
|
||||||
|
|| descriptor.presentationPath !== "/var/lib/nodedc-device-manager/device-manager-presentation.json"
|
||||||
|
|| descriptor.mediaRoot !== "/var/lib/nodedc-device-manager/media"
|
||||||
|
|| descriptor.defaultAccentHex !== "#f5f5f5"
|
||||||
|
|| descriptor.overviewLayout !== "mission-core-landing-stage-v1"
|
||||||
|
|| descriptor.faviconSet !== "nodedc-adaptive-v1"
|
||||||
|
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|
||||||
|
|| descriptor.gelios !== "untouched-legacy-only"
|
||||||
|
) throw new Error("device_manager_v6_favicon_contract_mismatch");
|
||||||
|
const faviconHashes = {
|
||||||
|
"favicon.ico": "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2",
|
||||||
|
"favicon/favicon.ico": "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2",
|
||||||
|
"favicon/icon-adaptive.svg": "481984e83997d786bb0a72ad1ee80037db13aef3a0792ab3109df95c2199b38e",
|
||||||
|
"favicon/apple-touch-icon.png": "afdccc28152a566e264e533ca218362f05d5bcec936c647f9a54f414c0bd4763",
|
||||||
|
"favicon/icon-192.png": "5b10a24feb4754f15c69761cef42f91a01f885d04095156b1a12e254875fdd4d",
|
||||||
|
"favicon/icon-512.png": "f98bac3dba59b7eefbe89f8bb8abc25567226a54ab7ed3b6b1a4caffbdd9ee15",
|
||||||
|
"favicon/manifest.webmanifest.json": "2a8ecdc6e6c64833f812ae02bbc0c7bd9b435e0cfa75cc21d6edf054d41275fc",
|
||||||
|
};
|
||||||
|
for (const [relativePath, expectedSha256] of Object.entries(faviconHashes)) {
|
||||||
|
const content = await readFile(join(payload, "services/device-manager/dist", relativePath));
|
||||||
|
const actualSha256 = createHash("sha256").update(content).digest("hex");
|
||||||
|
if (actualSha256 !== expectedSha256) throw new Error(`device_manager_v6_favicon_hash_mismatch:${relativePath}`);
|
||||||
|
}
|
||||||
|
const indexHtml = await readFile(join(payload, "services/device-manager/dist/index.html"), "utf8");
|
||||||
|
for (const requiredLink of [
|
||||||
|
'href="/favicon/icon-adaptive.svg"',
|
||||||
|
'href="/favicon/favicon.ico"',
|
||||||
|
'href="/favicon/apple-touch-icon.png"',
|
||||||
|
'href="/favicon/icon-192.png"',
|
||||||
|
'href="/favicon/icon-512.png"',
|
||||||
|
'href="/favicon/manifest.webmanifest.json"',
|
||||||
|
]) if (!indexHtml.includes(requiredLink)) throw new Error(`device_manager_v6_favicon_link_missing:${requiredLink}`);
|
||||||
|
} else if (descriptorPath.endsWith("release-v5.json")) {
|
||||||
if (
|
if (
|
||||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v5"
|
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v5"
|
||||||
|| descriptor.predecessor?.kind !== "release"
|
|| descriptor.predecessor?.kind !== "release"
|
||||||
|
|||||||
@@ -458,10 +458,15 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
|||||||
self.assertEqual(manifest["component"], "device-plane")
|
self.assertEqual(manifest["component"], "device-plane")
|
||||||
self.assertEqual(result["services"], ["device-manager"])
|
self.assertEqual(result["services"], ["device-manager"])
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
any(name.startswith("payload/services/device-control-core/") for name in names)
|
any(
|
||||||
|
name.startswith("payload/services/device-control-core/")
|
||||||
|
for name in names
|
||||||
|
)
|
||||||
)
|
)
|
||||||
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
|
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
|
||||||
self.assertFalse(any(name.endswith((".test.mjs", ".map")) for name in names))
|
self.assertFalse(
|
||||||
|
any(name.endswith((".test.mjs", ".map")) for name in names)
|
||||||
|
)
|
||||||
template = json.loads(
|
template = json.loads(
|
||||||
(
|
(
|
||||||
DEVICE_CORE_ROOT
|
DEVICE_CORE_ROOT
|
||||||
@@ -503,6 +508,68 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_device_manager_release_v6_includes_canonical_favicon_set(self):
|
||||||
|
patch_id = "device-manager-release-v6-unit-001"
|
||||||
|
manifest, entries, names, result = self.assert_deterministic_artifact(
|
||||||
|
"build-device-manager-control-plane-artifact.mjs",
|
||||||
|
patch_id,
|
||||||
|
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES,
|
||||||
|
)
|
||||||
|
self.assertEqual(manifest["component"], "device-plane")
|
||||||
|
self.assertEqual(result["services"], ["device-manager"])
|
||||||
|
self.assertFalse(
|
||||||
|
any(name.startswith("payload/services/device-control-core/") for name in names)
|
||||||
|
)
|
||||||
|
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
|
||||||
|
self.assertFalse(any(name.endswith((".test.mjs", ".map")) for name in names))
|
||||||
|
for name in (
|
||||||
|
"payload/services/device-manager/dist/favicon.ico",
|
||||||
|
"payload/services/device-manager/dist/favicon/favicon.ico",
|
||||||
|
"payload/services/device-manager/dist/favicon/icon-adaptive.svg",
|
||||||
|
"payload/services/device-manager/dist/favicon/apple-touch-icon.png",
|
||||||
|
"payload/services/device-manager/dist/favicon/icon-192.png",
|
||||||
|
"payload/services/device-manager/dist/favicon/icon-512.png",
|
||||||
|
"payload/services/device-manager/dist/favicon/manifest.webmanifest.json",
|
||||||
|
):
|
||||||
|
self.assertIn(name, names)
|
||||||
|
template = json.loads(
|
||||||
|
(
|
||||||
|
DEVICE_CORE_ROOT
|
||||||
|
/ "deployment/device-manager-release-v6.json"
|
||||||
|
).read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
descriptor = {**template, "releaseId": patch_id}
|
||||||
|
self.assertIs(
|
||||||
|
RUNNER.validate_device_plane_manager_release_descriptor(
|
||||||
|
descriptor,
|
||||||
|
schema_version=(
|
||||||
|
"nodedc.device-plane.device-manager-release.v6"
|
||||||
|
),
|
||||||
|
boundaries=(
|
||||||
|
RUNNER.expected_device_plane_manager_release_v6_boundaries()
|
||||||
|
),
|
||||||
|
expected_release_id=patch_id,
|
||||||
|
),
|
||||||
|
descriptor,
|
||||||
|
)
|
||||||
|
self.assertEqual(descriptor["faviconSet"], "nodedc-adaptive-v1")
|
||||||
|
self.assertEqual(
|
||||||
|
descriptor["predecessor"],
|
||||||
|
{
|
||||||
|
"kind": "release",
|
||||||
|
"patchId": "device-manager-release-v5-20260822-034",
|
||||||
|
"artifactSha256": (
|
||||||
|
"acc1d2ae2cda66861054826928c25d01a2428e688cc8132a9c381831bf29ab5a"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
RUNNER.is_device_plane_manager_release_v6_slice(
|
||||||
|
"device-plane",
|
||||||
|
entries,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_historical_manager_builder_fails_closed_after_v4_compose(self):
|
def test_historical_manager_builder_fails_closed_after_v4_compose(self):
|
||||||
if self.historical_manager_compose_is_current():
|
if self.historical_manager_compose_is_current():
|
||||||
self.skipTest("historical Manager Compose is still current")
|
self.skipTest("historical Manager Compose is still current")
|
||||||
|
|||||||
Reference in New Issue
Block a user