mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
test(web): qualify superseded admission and diagnose narrow table access
Close the missing third-organisation and real shell SIGTERM evidence without expanding product scope. Retain measured narrow-table clipping and accessible-name evidence so follow-up repairs preserve Android vertical gesture ownership rather than assuming horizontal scroll access. Validation: eight Chromium admission cases, two local-backend narrow socket/access runs, real Compose/Playwright SIGTERM cleanup, twelve focused shell tests, and diff checks passed. No installed release or physical-device qualification. Change-source: pulse-maintainer
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Full application, real organisation selector; synthetic HTTP and cold socket.
|
||||
// Run with PULSE_PROOF_WIDTH=390|1440 and PULSE_PROOF_MODE=failure|success|reverse.
|
||||
// Run with PULSE_PROOF_WIDTH=390|1440 and PULSE_PROOF_MODE=failure|success|reverse|superseded.
|
||||
// pulse-heavy-run -- node scripts/check-navigation-admission-race.mjs
|
||||
import { createServer } from "../frontend-modern/node_modules/vite/dist/node/index.js";
|
||||
import {
|
||||
@@ -18,9 +18,9 @@ const height = width === 390 ? 844 : 900;
|
||||
const mode = process.env.PULSE_PROOF_MODE || "failure";
|
||||
if (
|
||||
![390, 1440].includes(width) ||
|
||||
!["failure", "success", "reverse"].includes(mode)
|
||||
!["failure", "success", "reverse", "superseded"].includes(mode)
|
||||
) {
|
||||
throw new Error("Use width 390 or 1440 and mode failure, success or reverse");
|
||||
throw new Error("Use width 390 or 1440 and mode failure, success, reverse or superseded");
|
||||
}
|
||||
const phase =
|
||||
process.env.PULSE_PROOF_PHASE === "baseline" ? "baseline" : "repaired";
|
||||
@@ -95,6 +95,10 @@ try {
|
||||
outgoing = route;
|
||||
return;
|
||||
}
|
||||
if (mode === "superseded") {
|
||||
if (!incoming) { incoming = route; return; }
|
||||
return json(emptyAdmission);
|
||||
}
|
||||
incoming = route;
|
||||
if (mode === "reverse") return;
|
||||
if (mode === "success") return json(emptyAdmission);
|
||||
@@ -110,6 +114,7 @@ try {
|
||||
return json([
|
||||
{ id: "default", displayName: "Default Organization" },
|
||||
{ id: "acme", displayName: "Acme" },
|
||||
{ id: "third", displayName: "Third" },
|
||||
]);
|
||||
if (path === "/api/license/runtime-capabilities")
|
||||
return json({
|
||||
@@ -147,7 +152,17 @@ try {
|
||||
await org.selectOption("acme");
|
||||
await expect(org).toHaveValue("acme");
|
||||
await expect.poll(() => Boolean(incoming)).toBe(true);
|
||||
if (mode !== "reverse") {
|
||||
const expectedRequests = ["default", "default", "acme"];
|
||||
if (mode === "superseded") {
|
||||
const thirdResponse = page.waitForResponse(r =>
|
||||
r.url().includes("/api/resources?page=1&limit=1") &&
|
||||
r.request().headers()["x-pulse-org-id"] === "third");
|
||||
await org.selectOption("third");
|
||||
await (await thirdResponse).finished();
|
||||
await expect(org).toHaveValue("third");
|
||||
expectedRequests.push("third");
|
||||
}
|
||||
if (mode !== "reverse" && mode !== "superseded") {
|
||||
await expect
|
||||
.poll(() =>
|
||||
incoming
|
||||
@@ -197,18 +212,23 @@ try {
|
||||
);
|
||||
// Remove the transient switch toast so the narrow navigation is inspectable.
|
||||
const dismiss = page.getByRole("button", { name: "Dismiss notification" });
|
||||
for (const button of await dismiss.all()) await button.click();
|
||||
await expect(dismiss).toHaveCount(0);
|
||||
// Multiple switches animate/remove toasts; wait for their normal expiry rather
|
||||
// than retaining locators that can detach while Playwright waits for stability.
|
||||
await expect(dismiss).toHaveCount(0, { timeout: 15_000 });
|
||||
await page.screenshot({ path: `${output}/after-outgoing-response.png` });
|
||||
await expect(platform).toHaveCount(0);
|
||||
expect(requests).toEqual(["default", "default", "acme"]);
|
||||
expect(requests).toEqual(expectedRequests);
|
||||
expect(failed).toBe(mode === "failure" ? 1 : 0);
|
||||
if (mode === "reverse") {
|
||||
if (mode === "reverse" || mode === "superseded") {
|
||||
const newestResponse = page.waitForResponse(
|
||||
(r) => r.request() === incoming.request(),
|
||||
);
|
||||
await incoming.fulfill({ json: emptyAdmission });
|
||||
await incoming.fulfill({ json: mode === "superseded"
|
||||
? { aggregations: { platformAdmission: { proxmox: true, docker: true } } }
|
||||
: emptyAdmission });
|
||||
await (await newestResponse).finished();
|
||||
await page.evaluate(() => new Promise(resolve =>
|
||||
requestAnimationFrame(() => requestAnimationFrame(resolve))));
|
||||
await expect(platform).toHaveCount(0);
|
||||
}
|
||||
if (width < 1280) {
|
||||
@@ -235,7 +255,7 @@ try {
|
||||
hold = false;
|
||||
await org.selectOption("default");
|
||||
await expect(platform).toBeVisible();
|
||||
expect(requests).toEqual(["default", "default", "acme", "default"]);
|
||||
expect(requests).toEqual([...expectedRequests, "default"]);
|
||||
if (width < 1280) {
|
||||
await platform.click();
|
||||
const menu = page.getByRole("menu", {
|
||||
@@ -254,9 +274,9 @@ try {
|
||||
"websocket_reconnected",
|
||||
),
|
||||
);
|
||||
await expect.poll(() => requests.length).toBe(5);
|
||||
await expect.poll(() => requests.length).toBe(expectedRequests.length + 2);
|
||||
await expect(platform).toBeVisible();
|
||||
expect(requests).toEqual(["default", "default", "acme", "default", "default"]);
|
||||
expect(requests).toEqual([...expectedRequests, "default", "default"]);
|
||||
expect(await page.evaluate(() => performance.timeOrigin)).toBe(
|
||||
documentIdentity,
|
||||
);
|
||||
|
||||
@@ -122,6 +122,46 @@ for (const admissionFailure of [false, true]) {
|
||||
if (width === 390) await mobileDestinations(page);
|
||||
expect(await page.evaluate(() => performance.timeOrigin)).toBe(documentIdentity);
|
||||
await capture('recovered');
|
||||
if (width === 390 && !admissionFailure && process.env.PULSE_E2E_TABLE_ACCESS === '1') {
|
||||
const row = page.locator('tr[data-docker-container-row]').filter({ hasText: 'notification' }).first();
|
||||
const table = row.locator('xpath=ancestor::table');
|
||||
const wrapper = table.locator('..');
|
||||
const measure = () => wrapper.evaluate(el => ({
|
||||
width: el.clientWidth, scrollWidth: el.scrollWidth, left: el.scrollLeft,
|
||||
overflowX: getComputedStyle(el).overflowX, tabIndex: (el as HTMLElement).tabIndex,
|
||||
cells: Array.from(el.querySelector('tr[data-docker-container-row]')!.children).map(cell => ({
|
||||
text: cell.textContent, x: cell.getBoundingClientRect().x,
|
||||
width: cell.getBoundingClientRect().width,
|
||||
titles: Array.from(cell.querySelectorAll('[title]')).map(n => n.getAttribute('title')),
|
||||
})),
|
||||
}));
|
||||
await row.scrollIntoViewIfNeeded();
|
||||
const initial = await measure();
|
||||
await row.hover();
|
||||
await page.mouse.wheel(2000, 0);
|
||||
await page.waitForTimeout(350);
|
||||
const pointer = await measure();
|
||||
await capture('table-after-horizontal-wheel');
|
||||
await wrapper.evaluate(el => { el.scrollLeft = 0; });
|
||||
// Start on a real focusable descendant, without adding tabindex to the UI.
|
||||
const toggle = row.getByRole('button').first();
|
||||
await toggle.focus();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await page.waitForTimeout(350);
|
||||
const keyboard = await measure();
|
||||
const accessibleRow = await row.ariaSnapshot();
|
||||
await page.keyboard.press('Enter');
|
||||
await capture('table-keyboard-detail');
|
||||
const expanded = await toggle.getAttribute('aria-expanded');
|
||||
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
|
||||
const detailId = await toggle.getAttribute('aria-controls');
|
||||
const detailText = detailId ? await page.locator(`[id="${detailId}"]`).innerText() : null;
|
||||
await testInfo.attach('narrow-table-access', {
|
||||
body: JSON.stringify({ initial, pointer, keyboard, accessibleRow, expanded, detailText }, null, 2),
|
||||
contentType: 'application/json',
|
||||
});
|
||||
}
|
||||
|
||||
await testInfo.attach('environment', { body: JSON.stringify({ browser: browser.version(), width, height: width === 390 ? 844 : 900, zoom: 1, admissionFailure, failedAdmissions, before }), contentType: 'application/json' });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Access and interruption qualification — 5 September 2026
|
||||
|
||||
Base: `76fd854da1b42402ad742258297ff31461f1bb39`, plus accompanying test-only
|
||||
changes. Chromium 141.0.7390.37; local synthetic inventory. No product change.
|
||||
|
||||
## Access diagnosis
|
||||
|
||||
Fresh external reference: https://www.w3.org/WAI/WCAG22/Understanding/reflow.html
|
||||
(accessed September 5). W3C permits two-dimensional data-table scrolling but
|
||||
not indiscriminate loss of information. This challenges treating a clipped
|
||||
screenshot as either an automatic failure or a mobile pass. It supports access
|
||||
diagnosis, not demand for a new surface or a conformance assertion.
|
||||
|
||||
At 390×844, Docker's wrapper measures 362px client/scroll width, uses
|
||||
`overflow-x: clip`, and remains at scrollLeft 0 after horizontal wheel input
|
||||
and ArrowRight from an existing detail-toggle button. The last update cell is
|
||||
48.44px wide; its badge is visibly clipped in both viewed fresh wheel screenshots.
|
||||
This is not off-screen content recoverable by horizontal scrolling. Source
|
||||
`frontend-modern/src/index.css` applies `overflow: clip` to phone platform
|
||||
wrappers below 40rem. Do not simply restore nested scrolling: that rule addresses
|
||||
Android vertical gesture ownership, which needs regression protection.
|
||||
|
||||
The first-row accessibility snapshot retains the full update action despite
|
||||
badge clipping. A second run selects truncated `notifications-worker`: its
|
||||
accessibility snapshot retains the full name, Enter sets expanded=true, and
|
||||
rendered detail text includes the full hostname. The viewed detail screenshot
|
||||
still truncates the drawer heading. This proves accessible naming and keyboard
|
||||
expansion, not screen-reader/device testing or sighted access to every long value;
|
||||
scrolling to the identity field was not exercised. Pointer input here means
|
||||
desktop Chromium horizontal wheel, not Android touch.
|
||||
|
||||
Text receipts are human-readable reporter extracts, not JSON. Cell geometry
|
||||
samples the first container row even in the long-name run; accessibleRow and
|
||||
detailText describe the selected long-name row. The opt-in diagnostic does not
|
||||
assert that clipping is acceptable.
|
||||
|
||||
## Real full-stack interruption
|
||||
|
||||
`run-tests.sh multi-tenant` source-built mock and e2e_runtime Docker images and
|
||||
started isolated Compose. The watcher waited for an owned cookie file and live
|
||||
Chromium descendants, recorded project container inventory, then sent SIGTERM
|
||||
to the supervisor. `interruption.json`: exit 143; server/mock running and seed
|
||||
exited before the signal; no owned containers, volumes, sampled descendant PIDs
|
||||
or auth/report/result directories afterwards. No cookie contents retained.
|
||||
|
||||
An initial watcher did not recognise headless_shell; that run completed normally
|
||||
(six passes, one expected skip), so it was not interruption proof. The corrected
|
||||
run supplies the receipt. The reproduction watcher also explicitly fails if
|
||||
criteria are unmet. SIGINT/SIGHUP have existing stub coverage only. SIGKILL/host
|
||||
failure, every possible new descendant and preservation of unrelated stacks
|
||||
were not qualified. No shared runtime was interrupted or deployed.
|
||||
|
||||
## Reproduction and checks
|
||||
|
||||
From repository root, install locked frontend-modern and tests/integration
|
||||
packages and pinned Chromium:
|
||||
|
||||
```sh
|
||||
pulse-heavy-run -- python3 tests/qualification/navigation-reconnect/2026-09-05-access/interrupt.py
|
||||
pulse-heavy-run -- env PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 PULSE_MOCK_MODE=true PULSE_E2E_NAVIGATION_RECOVERY=1 PULSE_E2E_TABLE_ACCESS=1 PULSE_E2E_LOCAL_BACKEND_PORT=18765 npm --prefix tests/integration test -- tests/96-navigation-socket-recovery.spec.ts --project=chromium --grep '390px.*false'
|
||||
pulse-heavy-run -- bash -c 'set -e; for width in 390 1440; do for mode in failure success reverse superseded; do PULSE_PROOF_WIDTH=$width PULSE_PROOF_MODE=$mode node scripts/check-navigation-admission-race.mjs; done; done'
|
||||
node --test tests/integration/scripts/run-tests-interruption.test.mjs tests/integration/scripts/run-tests-cleanup.test.mjs
|
||||
```
|
||||
|
||||
Both narrow backend runs passed and stopped; both recorded backend PIDs were
|
||||
absent afterwards. Twelve focused shell/cleanup tests passed. No full repository
|
||||
suite. Admission results are retained separately. Multiple-switch toast
|
||||
locators initially detached; the harness now waits for normal expiry instead
|
||||
of clicking a changing list. This is a harness correction, not a product fix.
|
||||
|
||||
## Next maintenance
|
||||
|
||||
Preserve supersession and real interruption coverage. Investigate fitting phone
|
||||
update actions into allocated cells while retaining accessible naming, keyboard
|
||||
access and Android page-owned vertical scrolling. Check demand-ledger scope
|
||||
before product work; this pass introduced no bet or user-visible surface.
|
||||
Installed alert delivery, release acceptance and physical-device accessibility
|
||||
remain separate dependencies. Full-stack SIGTERM proof is no longer missing;
|
||||
no Docker blocker is established.
|
||||
@@ -0,0 +1,164 @@
|
||||
[
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 390,
|
||||
"height": 844
|
||||
},
|
||||
"mode": "failure",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 1,
|
||||
"verified_at": "2026-09-05T16:54:49.496Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
},
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 390,
|
||||
"height": 844
|
||||
},
|
||||
"mode": "success",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 0,
|
||||
"verified_at": "2026-09-05T16:55:12.726Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
},
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 390,
|
||||
"height": 844
|
||||
},
|
||||
"mode": "reverse",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 0,
|
||||
"verified_at": "2026-09-05T16:55:35.941Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
},
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 390,
|
||||
"height": 844
|
||||
},
|
||||
"mode": "superseded",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"third",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 0,
|
||||
"verified_at": "2026-09-05T16:55:59.091Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
},
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
"mode": "failure",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 1,
|
||||
"verified_at": "2026-09-05T16:56:19.211Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
},
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
"mode": "success",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 0,
|
||||
"verified_at": "2026-09-05T16:56:42.832Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
},
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
"mode": "reverse",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 0,
|
||||
"verified_at": "2026-09-05T16:57:00.634Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
},
|
||||
{
|
||||
"result": "passed",
|
||||
"browser": "141.0.7390.37",
|
||||
"viewport": {
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
"mode": "superseded",
|
||||
"phase": "repaired",
|
||||
"requests": [
|
||||
"default",
|
||||
"default",
|
||||
"acme",
|
||||
"third",
|
||||
"default",
|
||||
"default"
|
||||
],
|
||||
"failed": 0,
|
||||
"verified_at": "2026-09-05T16:57:19.715Z",
|
||||
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
import subprocess, time, re, pathlib, signal, json, os
|
||||
root=pathlib.Path.cwd(); out=root/'tmp/web-followup'; out.mkdir(parents=True, exist_ok=True); log=out/'interruption.log'
|
||||
with log.open('w') as f:
|
||||
p=subprocess.Popen(['bash','tests/integration/scripts/run-tests.sh','multi-tenant'],stdout=f,stderr=subprocess.STDOUT)
|
||||
run=None; before=None; interrupted=False
|
||||
try:
|
||||
deadline=time.time()+1200
|
||||
while p.poll() is None and time.time()<deadline:
|
||||
data=log.read_text(); m=re.search(r'Isolated integration project: (pulse-e2e-[a-f0-9]+)',data)
|
||||
if m: run=m[1]
|
||||
if run:
|
||||
auth=root/'tests/integration/tmp/playwright-auth'/run/'shared-cookie-session.json'
|
||||
procs=subprocess.check_output(['ps','-eo','pid,ppid,args'],text=True)
|
||||
# Require browser descendant and completed cookie bootstrap before signalling.
|
||||
descendants={p.pid}; rows=[]
|
||||
for line in procs.splitlines()[1:]:
|
||||
fields=line.strip().split(None,2)
|
||||
if len(fields)==3: rows.append((int(fields[0]),int(fields[1]),fields[2]))
|
||||
for _ in range(12):
|
||||
descendants.update(pid for pid,ppid,args in rows if ppid in descendants)
|
||||
browsers=[pid for pid,ppid,args in rows if pid in descendants and ('headless_shell' in args or '/chrome ' in args)]
|
||||
if auth.exists() and browsers:
|
||||
before=subprocess.check_output(['docker','ps','-a','--filter',f'label=com.docker.compose.project={run}','--format','{{.Names}} {{.State}}'],text=True)
|
||||
if 'running' in before:
|
||||
p.send_signal(signal.SIGTERM); interrupted=True; break
|
||||
time.sleep(.2)
|
||||
if not interrupted and p.poll() is None: p.send_signal(signal.SIGTERM)
|
||||
code=p.wait(timeout=60)
|
||||
after=subprocess.check_output(['docker','ps','-a','--filter',f'label=com.docker.compose.project={run}','--format','{{.Names}}'],text=True) if run else 'no run'
|
||||
volumes=subprocess.check_output(['docker','volume','ls','--filter',f'label=com.docker.compose.project={run}','--format','{{.Name}}'],text=True) if run else 'no run'
|
||||
remaining=[pid for pid in descendants if pathlib.Path(f'/proc/{pid}').exists()] if interrupted else []
|
||||
paths=[root/'tests/integration'/part/run for part in ['tmp/playwright-auth','test-results','playwright-report']] if run else []
|
||||
result=dict(run=run,interrupted=interrupted,exit=code,containers_before=before,containers_after=after,volumes_after=volumes,remaining_pids=remaining,owned_paths_remaining=[str(x.relative_to(root)) for x in paths if x.exists()])
|
||||
(out/'interruption.json').write_text(json.dumps(result,indent=2)+'\n'); print(json.dumps(result,indent=2))
|
||||
assert interrupted and code == 143, 'Did not interrupt authenticated Chromium'
|
||||
assert not after and not volumes and not remaining and not result['owned_paths_remaining'], 'Owned resources remain'
|
||||
finally:
|
||||
if p.poll() is None: p.send_signal(signal.SIGTERM); p.wait(timeout=60)
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"run": "pulse-e2e-c95ed0849f86ed9f8e32a964b082d150",
|
||||
"interrupted": true,
|
||||
"exit": 143,
|
||||
"containers_before": "pulse-e2e-c95ed0849f86ed9f8e32a964b082d150-server running\npulse-e2e-c95ed0849f86ed9f8e32a964b082d150-seed exited\npulse-e2e-c95ed0849f86ed9f8e32a964b082d150-mock running\n",
|
||||
"containers_after": "",
|
||||
"volumes_after": "",
|
||||
"remaining_pids": [],
|
||||
"owned_paths_remaining": []
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"initial": {
|
||||
"width": 362,
|
||||
"scrollWidth": 362,
|
||||
"left": 0,
|
||||
"overflowX": "clip",
|
||||
"tabIndex": -1,
|
||||
"cells": [
|
||||
{
|
||||
"text": "loki",
|
||||
"x": 9,
|
||||
"width": 108.59375,
|
||||
"titles": [
|
||||
"Expand details for loki",
|
||||
"Healthy",
|
||||
"loki"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "running",
|
||||
"x": 117.59375,
|
||||
"width": 48.4375,
|
||||
"titles": [
|
||||
"running"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "6%",
|
||||
"x": 166.03125,
|
||||
"width": 55.890625,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "74%",
|
||||
"x": 221.921875,
|
||||
"width": 63.34375,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "2",
|
||||
"x": 285.265625,
|
||||
"width": 37.25,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "Update",
|
||||
"x": 322.515625,
|
||||
"width": 48.4375,
|
||||
"titles": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"pointer": {
|
||||
"width": 362,
|
||||
"scrollWidth": 362,
|
||||
"left": 0,
|
||||
"overflowX": "clip",
|
||||
"tabIndex": -1,
|
||||
"cells": [
|
||||
{
|
||||
"text": "loki",
|
||||
"x": 9,
|
||||
"width": 108.59375,
|
||||
"titles": [
|
||||
"Expand details for loki",
|
||||
"Healthy",
|
||||
"loki"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "running",
|
||||
"x": 117.59375,
|
||||
"width": 48.4375,
|
||||
"titles": [
|
||||
"running"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "6%",
|
||||
"x": 166.03125,
|
||||
"width": 55.890625,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "74%",
|
||||
"x": 221.921875,
|
||||
"width": 63.34375,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "2",
|
||||
"x": 285.265625,
|
||||
"width": 37.25,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "Update",
|
||||
"x": 322.515625,
|
||||
"width": 48.4375,
|
||||
"titles": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"keyboard": {
|
||||
"width": 362,
|
||||
"scrollWidth": 362,
|
||||
"left": 0,
|
||||
"overflowX": "clip",
|
||||
"tabIndex": -1,
|
||||
"cells": [
|
||||
{
|
||||
"text": "loki",
|
||||
"x": 9,
|
||||
"width": 108.59375,
|
||||
"titles": [
|
||||
"Expand details for loki",
|
||||
"Healthy",
|
||||
"loki"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "running",
|
||||
"x": 117.59375,
|
||||
"width": 48.4375,
|
||||
"titles": [
|
||||
"running"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "6%",
|
||||
"x": 166.03125,
|
||||
"width": 55.890625,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "74%",
|
||||
"x": 221.921875,
|
||||
"width": 63.34375,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "2",
|
||||
"x": 285.265625,
|
||||
"width": 37.25,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "Update",
|
||||
"x": 322.515625,
|
||||
"width": 48.4375,
|
||||
"titles": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"accessibleRow": "- 'row "Expand details for loki loki running 6% 74% 2 Click to review and update Current: sha256:afe3f... Latest: sha256:2a043..."':
|
||||
- cell "Expand details for loki loki":
|
||||
- button "Expand details for loki"
|
||||
- text: loki
|
||||
- cell "running"
|
||||
- cell "6%"
|
||||
- cell "74%"
|
||||
- cell "2"
|
||||
- 'cell "Click to review and update Current: sha256:afe3f... Latest: sha256:2a043..."':
|
||||
- 'button "Click to review and update Current: sha256:afe3f... Latest: sha256:2a043..."':
|
||||
- img
|
||||
- text: Update",
|
||||
"expanded": "true"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,241 @@
|
||||
{
|
||||
"initial": {
|
||||
"width": 362,
|
||||
"scrollWidth": 362,
|
||||
"left": 0,
|
||||
"overflowX": "clip",
|
||||
"tabIndex": -1,
|
||||
"cells": [
|
||||
{
|
||||
"text": "loki",
|
||||
"x": 9,
|
||||
"width": 108.59375,
|
||||
"titles": [
|
||||
"Expand details for loki",
|
||||
"Healthy",
|
||||
"loki"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "running",
|
||||
"x": 117.59375,
|
||||
"width": 48.4375,
|
||||
"titles": [
|
||||
"running"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "3%",
|
||||
"x": 166.03125,
|
||||
"width": 55.890625,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "74%",
|
||||
"x": 221.921875,
|
||||
"width": 63.34375,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "3",
|
||||
"x": 285.265625,
|
||||
"width": 37.25,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "Current",
|
||||
"x": 322.515625,
|
||||
"width": 48.4375,
|
||||
"titles": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"pointer": {
|
||||
"width": 362,
|
||||
"scrollWidth": 362,
|
||||
"left": 0,
|
||||
"overflowX": "clip",
|
||||
"tabIndex": -1,
|
||||
"cells": [
|
||||
{
|
||||
"text": "loki",
|
||||
"x": 9,
|
||||
"width": 108.59375,
|
||||
"titles": [
|
||||
"Expand details for loki",
|
||||
"Healthy",
|
||||
"loki"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "running",
|
||||
"x": 117.59375,
|
||||
"width": 48.4375,
|
||||
"titles": [
|
||||
"running"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "3%",
|
||||
"x": 166.03125,
|
||||
"width": 55.890625,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "74%",
|
||||
"x": 221.921875,
|
||||
"width": 63.34375,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "3",
|
||||
"x": 285.265625,
|
||||
"width": 37.25,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "Current",
|
||||
"x": 322.515625,
|
||||
"width": 48.4375,
|
||||
"titles": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"keyboard": {
|
||||
"width": 362,
|
||||
"scrollWidth": 362,
|
||||
"left": 0,
|
||||
"overflowX": "clip",
|
||||
"tabIndex": -1,
|
||||
"cells": [
|
||||
{
|
||||
"text": "loki",
|
||||
"x": 9,
|
||||
"width": 108.59375,
|
||||
"titles": [
|
||||
"Expand details for loki",
|
||||
"Healthy",
|
||||
"loki"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "running",
|
||||
"x": 117.59375,
|
||||
"width": 48.4375,
|
||||
"titles": [
|
||||
"running"
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "3%",
|
||||
"x": 166.03125,
|
||||
"width": 55.890625,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "74%",
|
||||
"x": 221.921875,
|
||||
"width": 63.34375,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "3",
|
||||
"x": 285.265625,
|
||||
"width": 37.25,
|
||||
"titles": []
|
||||
},
|
||||
{
|
||||
"text": "Current",
|
||||
"x": 322.515625,
|
||||
"width": 48.4375,
|
||||
"titles": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"accessibleRow": "- row "Expand details for notifications-worker notifications-worker running 4% 49% 1 —":
|
||||
- cell "Expand details for notifications-worker notifications-worker":
|
||||
- button "Expand details for notifications-worker"
|
||||
- text: notifications-worker
|
||||
- cell "running"
|
||||
- cell "4%"
|
||||
- cell "49%"
|
||||
- cell "1"
|
||||
- cell "—"",
|
||||
"expanded": "true",
|
||||
"detailText": "notifications-worker
|
||||
Container
|
||||
Docker / Podman
|
||||
Overview
|
||||
History
|
||||
Manage
|
||||
RUNTIME CONTEXT
|
||||
Observed state\t
|
||||
Running
|
||||
|
||||
Uptime\t
|
||||
11d 18h
|
||||
|
||||
Last seen\t
|
||||
11s ago
|
||||
|
||||
CONTAINER
|
||||
Image\t
|
||||
ghcr.io/pulse-demo/notifications-worker:2026.04
|
||||
|
||||
Restarts\t
|
||||
1
|
||||
|
||||
Created\t
|
||||
17 days ago
|
||||
|
||||
Started\t
|
||||
11 days ago
|
||||
|
||||
Compose project\t
|
||||
orion-2
|
||||
|
||||
Compose service\t
|
||||
postgres
|
||||
|
||||
Labels\t
|
||||
com.docker.compose.project: orion-2
|
||||
com.docker.compose.service: postgres
|
||||
com.pulse.demo: true
|
||||
com.pulse.role.production: true
|
||||
com.pulse.role.queue: true
|
||||
com.pulse.role.worker: true
|
||||
environment: staging
|
||||
|
||||
IDENTITY
|
||||
Hostname\t
|
||||
notifications-worker
|
||||
|
||||
Primary ID\t
|
||||
app-container:orion-2-26f93461b215
|
||||
|
||||
Parent\t
|
||||
agent-679e9ef0038bd2b7
|
||||
|
||||
Discovery\t
|
||||
app-container:notifications-worker
|
||||
|
||||
Metrics Target\t
|
||||
app-container:orion-2-26f93461b215
|
||||
|
||||
Aliases\t
|
||||
app-container:orion-2-26f93461b215
|
||||
orion-2-26f93461b215
|
||||
orion-2-mock
|
||||
app-platform-01
|
||||
+4
|
||||
Relationship map
|
||||
|
||||
1 canonical relationship
|
||||
|
||||
CANONICAL RELATIONSHIPS
|
||||
notifications-worker
|
||||
→
|
||||
docker-network-0ab79ab291cd98fa
|
||||
Attached To
|
||||
100% confidence · Docker Adapter · last seen just now"
|
||||
}
|
||||
Reference in New Issue
Block a user