Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions nodescraper/command_artifact_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@
}
pre.stderr { color: var(--fail-fg); }
.no-results { color: var(--muted); padding: 24px; text-align: center; display: none; }
.copy-btn {
flex: 0 0 auto;
display: inline-flex; align-items: center; justify-content: center;
background: var(--bg); color: var(--muted);
border: 1px solid var(--border); border-radius: 6px;
padding: 5px 7px; cursor: pointer; line-height: 0;
transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.copy-btn:hover { background: var(--panel-hover); border-color: var(--accent); color: var(--text); }
.copy-btn.copied { color: var(--ok-fg); border-color: var(--ok-fg); }
.copy-btn svg { display: block; }
</style>
</head>
<body>
Expand All @@ -143,6 +154,9 @@
<span class="chevron">&#9656;</span>
<code class="title">{command}</code>
<span class="badge {badge_cls}">exit {exit_code}</span>
<button class="copy-btn" type="button" title="Copy output" aria-label="Copy output">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</summary>
<div class="body">
{stdout_block}
Expand Down Expand Up @@ -176,6 +190,51 @@
document.getElementById('collapseAll').addEventListener('click', () => {
items.forEach(d => d.open = false);
});

const copyButtons = Array.from(document.querySelectorAll('.copy-btn'));
const COPY_SVG = copyButtons.length ? copyButtons[0].innerHTML : '';
const CHECK_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';

function showCopied(btn) {
btn.classList.add('copied');
btn.innerHTML = CHECK_SVG;
if (btn._resetTimer) clearTimeout(btn._resetTimer);
btn._resetTimer = setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = COPY_SVG;
}, 1200);
}

function fallbackCopy(text, btn) {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.top = '-1000px';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.focus();
ta.select();
try { document.execCommand('copy'); showCopied(btn); } catch (e) {}
document.body.removeChild(ta);
}

copyButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const card = btn.closest('details.cmd');
const parts = [];
card.querySelectorAll('pre').forEach(pre => {
if (!pre.classList.contains('empty')) parts.push(pre.textContent);
});
const text = parts.join('\\n');
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => showCopied(btn)).catch(() => fallbackCopy(text, btn));
} else {
fallbackCopy(text, btn);
}
});
});
</script>
</body>
</html>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#
###############################################################################

from typing import ClassVar, Dict, List, Optional, Union
from typing import ClassVar, Dict, List, Optional

from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
Expand Down Expand Up @@ -98,8 +98,8 @@ class FanConfiguration(BaseModel):
speed_hw_override: Optional[bool] = None
speed_stable: Optional[bool] = None

error_fields: ClassVar[dict[str, str]] = {
"status": "ok",
error_fields: ClassVar[dict[str, List[str]]] = {
"status": ["ok"],
}


Expand All @@ -124,9 +124,9 @@ class AristaSystemEnv(BaseModel):
power_supply_slots: Optional[List[FanConfiguration]] = None
fan_tray_slots: Optional[List[FanConfiguration]] = None

error_fields: ClassVar[dict[str, Union[str, bool]]] = {
"system_status": "coolingOk",
"fans_status": "fanAlarmOk",
error_fields: ClassVar[dict[str, List[str]]] = {
"system_status": ["coolingOk"],
"fans_status": ["fanAlarmOk"],
}


Expand All @@ -139,9 +139,9 @@ class VlanInformation(BaseModel):
interface_mode: Optional[str] = None
interface_forwarding_model: Optional[str] = None

error_fields: ClassVar[dict[str, str]] = {
"interface_mode": "routed",
"interface_forwarding_model": "routed",
error_fields: ClassVar[dict[str, List[str]]] = {
"interface_mode": ["routed"],
"interface_forwarding_model": ["routed"],
}


Expand All @@ -160,10 +160,10 @@ class AristaPortStatus(BaseModel):
line_protocol_status: Optional[str] = None
interface_damped: Optional[bool] = None

error_fields: ClassVar[dict[str, str]] = {
"link_status": "connected",
"duplex": "duplexFull",
"line_protocol_status": "up",
error_fields: ClassVar[dict[str, List[str]]] = {
"link_status": ["connected"],
"duplex": ["duplexFull"],
"line_protocol_status": ["up"],
}


Expand All @@ -180,14 +180,14 @@ class AristaCountersErrors(BaseModel):
alignment_errors: Optional[int] = None
symbol_errors: Optional[int] = None

error_fields: ClassVar[dict[str, str]] = {
"in_errors": "0",
"frame_too_longs": "0",
"out_errors": "0",
"frame_too_shorts": "0",
"fcs_errors": "0",
"alignment_errors": "0",
"symbol_errors": "0",
error_fields: ClassVar[dict[str, List[str]]] = {
"in_errors": ["0"],
"frame_too_longs": ["0"],
"out_errors": ["0"],
"frame_too_shorts": ["0"],
"fcs_errors": ["0"],
"alignment_errors": ["0"],
"symbol_errors": ["0"],
}


Expand Down Expand Up @@ -259,10 +259,10 @@ class AristaDroppedPacketCounters(BaseModel):
out_uc_dropped_pkts: Optional[int] = None
out_mc_dropped_pkts: Optional[int] = None

warning_fields: ClassVar[dict[str, str]] = {
"in_dropped_pkts": "0",
"out_uc_dropped_pkts": "0",
"out_mc_dropped_pkts": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"in_dropped_pkts": ["0"],
"out_uc_dropped_pkts": ["0"],
"out_mc_dropped_pkts": ["0"],
}


Expand All @@ -275,10 +275,10 @@ class AristaDropPrecedenceCounters(BaseModel):
dp1_dropped_pkts: Optional[int] = None
dp2_dropped_pkts: Optional[int] = None

error_fields: ClassVar[dict[str, str]] = {
"dp0_dropped_pkts": "0",
"dp1_dropped_pkts": "0",
"dp2_dropped_pkts": "0",
error_fields: ClassVar[dict[str, List[str]]] = {
"dp0_dropped_pkts": ["0"],
"dp1_dropped_pkts": ["0"],
"dp2_dropped_pkts": ["0"],
}


Expand All @@ -293,9 +293,9 @@ class AristaPerQueueCounters(BaseModel):
pkts_drop: Optional[int] = None
bytes_drop: Optional[int] = None

warning_fields: ClassVar[dict[str, str]] = {
"pkts_drop": "0",
"bytes_drop": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"pkts_drop": ["0"],
"bytes_drop": ["0"],
}


Expand All @@ -311,9 +311,9 @@ class AristaPauseFrameCounters(BaseModel):
tx_pause: Optional[int] = None
rx_pause: Optional[int] = None

error_fields: ClassVar[dict[str, str]] = {
"tx_pause": "0",
"rx_pause": "0",
error_fields: ClassVar[dict[str, List[str]]] = {
"tx_pause": ["0"],
"rx_pause": ["0"],
}


Expand All @@ -325,8 +325,8 @@ class AristaEcnCounters(BaseModel):
txq: Optional[str] = None
marked_packets: Optional[str] = None

warning_fields: ClassVar[dict[str, str]] = {
"marked_packets": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"marked_packets": ["0", "-"],
}


Expand All @@ -338,9 +338,9 @@ class AristaPfcCounters(BaseModel):
rx_frames: Optional[int] = None
tx_frames: Optional[int] = None

warning_fields: ClassVar[dict[str, str]] = {
"rx_frames": "0",
"tx_frames": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"rx_frames": ["0"],
"tx_frames": ["0"],
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ class DellArpEntry(BaseModel):
type: Optional[str] = None
action: Optional[str] = None

error_fields: ClassVar[dict[str, str]] = {
"address": "NOT_NULL",
"hardware_address": "NOT_NULL",
error_fields: ClassVar[dict[str, List[str]]] = {
"address": ["NOT_NULL"],
"hardware_address": ["NOT_NULL"],
}


Expand All @@ -62,8 +62,8 @@ class DellRouteEntry(BaseModel):
distance_metric: Optional[str] = None
last_update: Optional[str] = None

error_fields: ClassVar[dict[str, str]] = {
"destination": "NOT_NULL",
error_fields: ClassVar[dict[str, List[str]]] = {
"destination": ["NOT_NULL"],
}


Expand All @@ -81,8 +81,8 @@ class DellInterfaceStatus(BaseModel):
mtu: Optional[int] = None
alternate_name: Optional[str] = None

error_fields: ClassVar[dict[str, str]] = {
"oper": "up",
error_fields: ClassVar[dict[str, List[str]]] = {
"oper": ["up"],
}


Expand Down Expand Up @@ -112,17 +112,17 @@ class DellInterfaceCounters(BaseModel):
tx_drp: Optional[int] = None
tx_oversize: Optional[int] = None

error_fields: ClassVar[dict[str, str]] = {
"state": "U",
"rx_err": "0",
"rx_oversize": "0",
"tx_err": "0",
"tx_oversize": "0",
error_fields: ClassVar[dict[str, List[str]]] = {
"state": ["U"],
"rx_err": ["0"],
"rx_oversize": ["0"],
"tx_err": ["0"],
"tx_oversize": ["0"],
}

warning_fields: ClassVar[dict[str, str]] = {
"rx_drp": "0",
"tx_drp": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"rx_drp": ["0"],
"tx_drp": ["0"],
}


Expand Down Expand Up @@ -168,14 +168,14 @@ class DellInterfaceDetailCounters(BaseModel):

time_since_counters_last_cleared: Optional[str] = None

error_fields: ClassVar[dict[str, str]] = {
"packets_received_9217_16383_octets": "0",
"packets_transmitted_9217_16383_octets": "0",
"jabbers_received": "0",
"fragments_received": "0",
"undersize_received": "0",
"overruns_received": "0",
"crc_errors_received": "0",
error_fields: ClassVar[dict[str, List[str]]] = {
"packets_received_9217_16383_octets": ["0"],
"packets_transmitted_9217_16383_octets": ["0"],
"jabbers_received": ["0"],
"fragments_received": ["0"],
"undersize_received": ["0"],
"overruns_received": ["0"],
"crc_errors_received": ["0"],
}


Expand All @@ -193,9 +193,9 @@ class DellQueueCounter(BaseModel):
drop_pkts: Optional[int] = None
drop_bytes: Optional[int] = None

warning_fields: ClassVar[dict[str, str]] = {
"drop_pkts": "0",
"drop_bytes": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"drop_pkts": ["0"],
"drop_bytes": ["0"],
}


Expand All @@ -217,15 +217,15 @@ class DellPfcStatistics(BaseModel):
pfc6: Optional[int] = None
pfc7: Optional[int] = None

warning_fields: ClassVar[dict[str, str]] = {
"pfc0": "0",
"pfc1": "0",
"pfc2": "0",
"pfc3": "0",
"pfc4": "0",
"pfc5": "0",
"pfc6": "0",
"pfc7": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"pfc0": ["0"],
"pfc1": ["0"],
"pfc2": ["0"],
"pfc3": ["0"],
"pfc4": ["0"],
"pfc5": ["0"],
"pfc6": ["0"],
"pfc7": ["0"],
}


Expand All @@ -247,17 +247,17 @@ class DellPfcWatchdogQueueStats(BaseModel):
rx_last_ok: Optional[int] = None
rx_last_drop: Optional[int] = None

warning_fields: ClassVar[dict[str, str]] = {
"storms_detected": "0",
"storms_restored": "0",
"transmitted_ok": "0",
"transmitted_drop": "0",
"received_ok": "0",
"received_drop": "0",
"tx_last_ok": "0",
"tx_last_drop": "0",
"rx_last_ok": "0",
"rx_last_drop": "0",
warning_fields: ClassVar[dict[str, List[str]]] = {
"storms_detected": ["0"],
"storms_restored": ["0"],
"transmitted_ok": ["0"],
"transmitted_drop": ["0"],
"received_ok": ["0"],
"received_drop": ["0"],
"tx_last_ok": ["0"],
"tx_last_drop": ["0"],
"rx_last_ok": ["0"],
"rx_last_drop": ["0"],
}


Expand Down
Loading