@leavepulse/ui 0.22.2 → 0.22.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ const _hoisted_8 = {
16
16
  };
17
17
  const _hoisted_9 = ["title"];
18
18
  const _hoisted_10 = ["title"];
19
+ const _hoisted_11 = ["title"];
19
20
  const _sfc_main = /* @__PURE__ */ defineComponent({
20
21
  __name: "LpInfraNode",
21
22
  props: {
@@ -57,7 +58,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
57
58
  position: unref(Position).Right,
58
59
  class: "!size-2 !border-2 !border-[var(--accent)] !bg-surface"
59
60
  }, null, 8, ["position"]),
60
- _cache[2] || (_cache[2] = createElementVNode("div", { class: "h-[3px] bg-[var(--accent)]" }, null, -1)),
61
+ _cache[3] || (_cache[3] = createElementVNode("div", { class: "h-[3px] bg-[var(--accent)]" }, null, -1)),
61
62
  createElementVNode("div", _hoisted_1, [
62
63
  createElementVNode("div", _hoisted_2, [
63
64
  createElementVNode("span", _hoisted_3, toDisplayString(icon.value), 1),
@@ -71,7 +72,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
71
72
  createElementVNode("span", _hoisted_6, toDisplayString(__props.data.role), 1),
72
73
  __props.data.overlay ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(__props.data.overlay), 1)) : createCommentVNode("", true)
73
74
  ]),
74
- __props.data.firewallCount || __props.data.serviceCount !== void 0 ? (openBlock(), createElementBlock("div", _hoisted_8, [
75
+ __props.data.firewallCount || __props.data.serviceCount !== void 0 || __props.data.staleFor ? (openBlock(), createElementBlock("div", _hoisted_8, [
75
76
  __props.data.firewallCount ? (openBlock(), createElementBlock("span", {
76
77
  key: 0,
77
78
  class: "inline-flex items-center gap-1 rounded-pill bg-danger-soft px-1.5 py-0.5 text-[9px] font-medium text-danger",
@@ -82,12 +83,20 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
82
83
  ], 8, _hoisted_9)) : createCommentVNode("", true),
83
84
  __props.data.serviceCount !== void 0 ? (openBlock(), createElementBlock("span", {
84
85
  key: 1,
85
- class: "inline-flex items-center gap-1 rounded-pill bg-surface-soft px-1.5 py-0.5 text-[9px] font-medium text-muted",
86
- title: `${__props.data.serviceCount} service(s)`
86
+ class: normalizeClass(["inline-flex items-center gap-1 rounded-pill bg-surface-soft px-1.5 py-0.5 text-[9px] font-medium text-muted", __props.data.staleFor ? "line-through decoration-danger/70" : ""]),
87
+ title: __props.data.staleFor ? `${__props.data.serviceCount} service(s) as of ${__props.data.staleFor} ago — not current` : `${__props.data.serviceCount} service(s)`
87
88
  }, [
88
89
  _cache[1] || (_cache[1] = createElementVNode("span", null, "◇", -1)),
89
90
  createTextVNode(toDisplayString(__props.data.serviceCount), 1)
90
- ], 8, _hoisted_10)) : createCommentVNode("", true)
91
+ ], 10, _hoisted_10)) : createCommentVNode("", true),
92
+ __props.data.staleFor ? (openBlock(), createElementBlock("span", {
93
+ key: 2,
94
+ class: "inline-flex items-center gap-1 rounded-pill bg-danger-soft px-1.5 py-0.5 text-[9px] font-medium text-danger",
95
+ title: `No contact for ${__props.data.staleFor} — everything shown here is that old`
96
+ }, [
97
+ _cache[2] || (_cache[2] = createElementVNode("span", null, "⚠", -1)),
98
+ createTextVNode(toDisplayString(__props.data.staleFor) + " old ", 1)
99
+ ], 8, _hoisted_11)) : createCommentVNode("", true)
91
100
  ])) : createCommentVNode("", true)
92
101
  ])
93
102
  ], 6);
@@ -3,7 +3,7 @@ import { Background } from "@vue-flow/background";
3
3
  import { Controls } from "@vue-flow/controls";
4
4
  import { MarkerType, useVueFlow, VueFlow, SelectionMode, Panel } from "@vue-flow/core";
5
5
  import { MiniMap } from "@vue-flow/minimap";
6
- import { _ as _sfc_main$3 } from "./LpInfraNode-C_PHcthC.js";
6
+ import { _ as _sfc_main$3 } from "./LpInfraNode-C_4cyGl9.js";
7
7
  import { _ as _sfc_main$1 } from "./LpLaneNode-HtnQf2FF.js";
8
8
  import { _ as _sfc_main$2 } from "./LpServiceNode-Dc-Ut5RS.js";
9
9
  const _hoisted_1 = { class: "flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur" };
@@ -103,7 +103,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
103
103
  source: e.source,
104
104
  target: e.target,
105
105
  type: "default",
106
- animated: obs !== "applied",
106
+ animated: obs !== "applied" && !e.dimmed,
107
107
  label: e.kind,
108
108
  labelBgStyle: { fill: "var(--color-surface)" },
109
109
  labelStyle: { fill: "var(--color-muted-strong)", fontSize: "10px" },
@@ -111,7 +111,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
111
111
  stroke: color,
112
112
  strokeWidth: cat === "structural" ? 1.5 : 2,
113
113
  strokeDasharray: edgeDash(cat, obs),
114
- strokeLinecap: "round"
114
+ strokeLinecap: "round",
115
+ opacity: e.dimmed ? 0.12 : 1
115
116
  },
116
117
  markerEnd: { type: MarkerType.ArrowClosed, color }
117
118
  };
@@ -9,6 +9,13 @@ export interface InfraNodeData {
9
9
  firewallCount?: number;
10
10
  /** Service count on this host (badge when defined — services toggle on). */
11
11
  serviceCount?: number;
12
+ /**
13
+ * How old this node's readings are, human-readable ("28d"). An unreachable
14
+ * machine keeps serving its last snapshot, which reads as current unless the
15
+ * card says otherwise — so when set, the counts below are struck through and
16
+ * the age is shown in their place.
17
+ */
18
+ staleFor?: string;
12
19
  /** Fade the node back (search miss / not part of the focused node's graph). */
13
20
  dimmed?: boolean;
14
21
  }
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpInfraNode-C_PHcthC.js";
1
+ import { _ as _sfc_main } from "../chunks/LpInfraNode-C_4cyGl9.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -40,6 +40,12 @@ export interface TopologyEdge {
40
40
  /** Semantic class driving colour + dash. Inferred from `kind` if omitted. */
41
41
  category?: EdgeCategory;
42
42
  observed?: EdgeObserved;
43
+ /**
44
+ * Push the edge into the background, matching the node data's `dimmed`. A
45
+ * focus highlight has to fade the edges too — dimming only the nodes leaves
46
+ * every unrelated line at full strength.
47
+ */
48
+ dimmed?: boolean;
43
49
  }
44
50
  /**
45
51
  * A background swimlane: a sized zone (one per project / overlay network) drawn
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTopologyCanvas-CQt-G9KL.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTopologyCanvas-74i_pq4e.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import { _ as _23 } from "./chunks/LpFileTree-DWx77B1F.js";
26
26
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
27
27
  import { _ as _24 } from "./chunks/LpFormField-Bn-ZwC6z.js";
28
28
  import { _ as _25 } from "./chunks/LpIcon-CCnX5_2j.js";
29
- import { _ as _26 } from "./chunks/LpInfraNode-C_PHcthC.js";
29
+ import { _ as _26 } from "./chunks/LpInfraNode-C_4cyGl9.js";
30
30
  import { _ as _27 } from "./chunks/LpInput-BauZRyzm.js";
31
31
  import { _ as _28 } from "./chunks/LpLightbox-Ci35Y_xP.js";
32
32
  import { fileNameOf } from "./components/lightbox.js";
@@ -64,7 +64,7 @@ import { D, _ as _56, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v }
64
64
  import { _ as _57 } from "./chunks/LpTilt-DuyKbpkA.js";
65
65
  import { _ as _58 } from "./chunks/LpToaster-gDB1EaU3.js";
66
66
  import { _ as _59 } from "./chunks/LpTooltip-D1hbjQWG.js";
67
- import { _ as _60 } from "./chunks/LpTopologyCanvas-CQt-G9KL.js";
67
+ import { _ as _60 } from "./chunks/LpTopologyCanvas-74i_pq4e.js";
68
68
  import { _ as _61 } from "./chunks/LpUptimeBar-CB366k0O.js";
69
69
  import { deserializeLayout, serializeLayout } from "./layout/tree.js";
70
70
  import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.22.2",
3
+ "version": "0.22.4",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -22,6 +22,13 @@ export interface InfraNodeData {
22
22
  firewallCount?: number
23
23
  /** Service count on this host (badge when defined — services toggle on). */
24
24
  serviceCount?: number
25
+ /**
26
+ * How old this node's readings are, human-readable ("28d"). An unreachable
27
+ * machine keeps serving its last snapshot, which reads as current unless the
28
+ * card says otherwise — so when set, the counts below are struck through and
29
+ * the age is shown in their place.
30
+ */
31
+ staleFor?: string
25
32
  /** Fade the node back (search miss / not part of the focused node's graph). */
26
33
  dimmed?: boolean
27
34
  }
@@ -85,7 +92,7 @@ const icon = computed(() => roleIcon[props.data.role] ?? "●")
85
92
  <span v-if="data.overlay" class="font-mono text-[10px] text-muted">{{ data.overlay }}</span>
86
93
  </div>
87
94
  <div
88
- v-if="data.firewallCount || data.serviceCount !== undefined"
95
+ v-if="data.firewallCount || data.serviceCount !== undefined || data.staleFor"
89
96
  class="mt-1.5 flex items-center gap-1.5"
90
97
  >
91
98
  <span
@@ -98,10 +105,22 @@ const icon = computed(() => roleIcon[props.data.role] ?? "●")
98
105
  <span
99
106
  v-if="data.serviceCount !== undefined"
100
107
  class="inline-flex items-center gap-1 rounded-pill bg-surface-soft px-1.5 py-0.5 text-[9px] font-medium text-muted"
101
- :title="`${data.serviceCount} service(s)`"
108
+ :class="data.staleFor ? 'line-through decoration-danger/70' : ''"
109
+ :title="
110
+ data.staleFor
111
+ ? `${data.serviceCount} service(s) as of ${data.staleFor} ago — not current`
112
+ : `${data.serviceCount} service(s)`
113
+ "
102
114
  >
103
115
  <span>◇</span>{{ data.serviceCount }}
104
116
  </span>
117
+ <span
118
+ v-if="data.staleFor"
119
+ class="inline-flex items-center gap-1 rounded-pill bg-danger-soft px-1.5 py-0.5 text-[9px] font-medium text-danger"
120
+ :title="`No contact for ${data.staleFor} — everything shown here is that old`"
121
+ >
122
+ <span>⚠</span>{{ data.staleFor }} old
123
+ </span>
105
124
  </div>
106
125
  </div>
107
126
  </div>
@@ -72,12 +72,8 @@ const widthClass = computed(() => {
72
72
  // inset horizontally; top only when there's no header, bottom only when there's
73
73
  // no footer (header/footer own those edges).
74
74
  const slots = useSlots()
75
- /*
76
- * The body clips its overflow, and a focus ring is drawn OUTSIDE the control it
77
- * belongs to — so a field sitting flush against the top of the body had its
78
- * ring sliced off. Under a header the gap is only the ring's width plus a
79
- * hair; without one the existing pt-5 already clears it.
80
- */
75
+ // The body clips overflow and focus rings are drawn outside their control, so a
76
+ // field flush against the top edge lost its ring; pt-1 clears the ring's width.
81
77
  const bodyPad = computed(() =>
82
78
  [
83
79
  "px-5",
@@ -71,6 +71,12 @@ export interface TopologyEdge {
71
71
  /** Semantic class driving colour + dash. Inferred from `kind` if omitted. */
72
72
  category?: EdgeCategory
73
73
  observed?: EdgeObserved
74
+ /**
75
+ * Push the edge into the background, matching the node data's `dimmed`. A
76
+ * focus highlight has to fade the edges too — dimming only the nodes leaves
77
+ * every unrelated line at full strength.
78
+ */
79
+ dimmed?: boolean
74
80
  }
75
81
 
76
82
  /**
@@ -248,7 +254,7 @@ const flowEdges = computed(() =>
248
254
  source: e.source,
249
255
  target: e.target,
250
256
  type: "default",
251
- animated: obs !== "applied",
257
+ animated: obs !== "applied" && !e.dimmed,
252
258
  label: e.kind,
253
259
  labelBgStyle: { fill: "var(--color-surface)" },
254
260
  labelStyle: { fill: "var(--color-muted-strong)", fontSize: "10px" },
@@ -257,6 +263,7 @@ const flowEdges = computed(() =>
257
263
  strokeWidth: cat === "structural" ? 1.5 : 2,
258
264
  strokeDasharray: edgeDash(cat, obs),
259
265
  strokeLinecap: "round" as const,
266
+ opacity: e.dimmed ? 0.12 : 1,
260
267
  },
261
268
  markerEnd: { type: MarkerType.ArrowClosed, color },
262
269
  }