@skyhook-io/k8s-ui 1.8.15 → 1.9.1

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.
@@ -1,4 +1,4 @@
1
- import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
1
+ import { useState, useMemo, useCallback, useDeferredValue, useEffect, useRef } from 'react'
2
2
  import type { VirtuosoHandle } from 'react-virtuoso'
3
3
  import type { LogEntry } from './useLogBuffer'
4
4
  import { stripAnsi, escapeRegExp } from '../../utils/log-format'
@@ -40,17 +40,22 @@ export function useLogSearch(
40
40
  const [currentMatch, setCurrentMatch] = useState(0)
41
41
  const [isOpen, setIsOpen] = useState(false)
42
42
 
43
+ // The scan strips ANSI and regex-tests every buffered line — on a multi-MB
44
+ // buffer that's too slow to run synchronously per keystroke. Deferring lets
45
+ // the input echo immediately while the match set catches up.
46
+ const deferredQuery = useDeferredValue(query)
47
+
43
48
  const { matchIndices, regexError } = useMemo(() => {
44
- if (!query) {
49
+ if (!deferredQuery) {
45
50
  return { matchIndices: [] as number[], regexError: null }
46
51
  }
47
52
 
48
53
  try {
49
54
  let pattern: RegExp
50
55
  if (isRegex) {
51
- pattern = new RegExp(query, isCaseSensitive ? 'g' : 'gi')
56
+ pattern = new RegExp(deferredQuery, isCaseSensitive ? 'g' : 'gi')
52
57
  } else {
53
- pattern = new RegExp(escapeRegExp(query), isCaseSensitive ? 'g' : 'gi')
58
+ pattern = new RegExp(escapeRegExp(deferredQuery), isCaseSensitive ? 'g' : 'gi')
54
59
  }
55
60
 
56
61
  const indices: number[] = []
@@ -65,14 +70,14 @@ export function useLogSearch(
65
70
  } catch (e) {
66
71
  return { matchIndices: [] as number[], regexError: e instanceof Error ? e.message : 'Invalid regex' }
67
72
  }
68
- }, [entries, query, isRegex, isCaseSensitive])
73
+ }, [entries, deferredQuery, isRegex, isCaseSensitive])
69
74
 
70
75
  // Filtered entries for filter mode
71
76
  const filteredEntries = useMemo(() => {
72
- if (!isFilterMode || !query) return entries
77
+ if (!isFilterMode || !deferredQuery) return entries
73
78
  const matchSet = new Set(matchIndices)
74
79
  return entries.filter((_, i) => matchSet.has(i))
75
- }, [entries, isFilterMode, query, matchIndices])
80
+ }, [entries, isFilterMode, deferredQuery, matchIndices])
76
81
 
77
82
  // Reset current match when search criteria change (but not when new entries arrive during streaming)
78
83
  const prevCriteria = useRef({ query, isRegex, isCaseSensitive })
@@ -0,0 +1,111 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { ContainerResourceMetrics } from '../../types'
3
+ import { podAggregate, readContainer } from './ResourcesView'
4
+
5
+ // Build a CPU-only container fixture; memory fields default to 0.
6
+ function cpu(name: string, usage: number, request: number, limit: number): ContainerResourceMetrics {
7
+ return { name, cpu: usage, cpuRequest: request, cpuLimit: limit, memory: 0, memoryRequest: 0, memoryLimit: 0 }
8
+ }
9
+
10
+ // Build a memory-only container fixture; cpu fields default to 0.
11
+ function mem(name: string, usage: number, request: number, limit: number): ContainerResourceMetrics {
12
+ return { name, cpu: 0, cpuRequest: 0, cpuLimit: 0, memory: usage, memoryRequest: request, memoryLimit: limit }
13
+ }
14
+
15
+ describe('readContainer', () => {
16
+ it("uses limit as the yardstick when a limit is set", () => {
17
+ const r = readContainer(cpu('app', 50, 25, 100), 'cpu')
18
+ expect(r.yardstick).toBe('limit')
19
+ expect(r.denom).toBe(100)
20
+ expect(r.pct).toBe(50)
21
+ })
22
+
23
+ it('falls back to request when limit is 0 but request is set', () => {
24
+ const r = readContainer(cpu('app', 80, 100, 0), 'cpu')
25
+ expect(r.yardstick).toBe('request')
26
+ expect(r.denom).toBe(100)
27
+ expect(r.pct).toBe(80)
28
+ })
29
+
30
+ it('allows request-only pct to exceed 100%', () => {
31
+ const r = readContainer(cpu('app', 200, 100, 0), 'cpu')
32
+ expect(r.yardstick).toBe('request')
33
+ expect(r.pct).toBe(200)
34
+ })
35
+
36
+ it("reports 'none' when neither request nor limit is set", () => {
37
+ const r = readContainer(cpu('app', 40, 0, 0), 'cpu')
38
+ expect(r.yardstick).toBe('none')
39
+ expect(r.pct).toBe(-1)
40
+ })
41
+
42
+ it('reads the memory fields for the memory kind', () => {
43
+ const r = readContainer(mem('app', 64, 32, 128), 'memory')
44
+ expect(r.yardstick).toBe('limit')
45
+ expect(r.denom).toBe(128)
46
+ expect(r.pct).toBe(50)
47
+ })
48
+ })
49
+
50
+ describe('podAggregate', () => {
51
+ it("reports mode 'limit' with the summed limit and request marker when every container is limited", () => {
52
+ const containers = [
53
+ cpu('app', 30, 50, 100),
54
+ cpu('sidecar', 40, 50, 200),
55
+ ]
56
+ const result = podAggregate(containers, 'cpu')
57
+ expect(result.mode).toBe('limit')
58
+ expect(result.totalUsage).toBe(70)
59
+ expect(result.denom).toBe(300) // summed limit
60
+ expect(result.markerPct).toBeCloseTo((100 / 300) * 100) // summed request / summed limit
61
+ expect(result.unlimitedCount).toBe(0)
62
+ })
63
+
64
+ it("omits the marker in 'limit' mode when no container sets a request", () => {
65
+ const containers = [cpu('a', 30, 0, 100), cpu('b', 40, 0, 200)]
66
+ const result = podAggregate(containers, 'cpu')
67
+ expect(result.mode).toBe('limit')
68
+ expect(result.markerPct).toBeUndefined()
69
+ })
70
+
71
+ it("reports mode 'partial' with usage and unbounded count when only some containers are limited", () => {
72
+ const containers = [
73
+ cpu('app', 80, 0, 0), // dominant, unbounded
74
+ cpu('sidecar', 5, 0, 100), // small, limited
75
+ ]
76
+ const result = podAggregate(containers, 'cpu')
77
+ expect(result.mode).toBe('partial')
78
+ expect(result.totalUsage).toBe(85)
79
+ expect(result.denom).toBe(0) // a partial limit sum is not a real ceiling
80
+ expect(result.unlimitedCount).toBe(1)
81
+ })
82
+
83
+ it("reports mode 'request' with the summed request when there are no limits but some requests", () => {
84
+ const containers = [
85
+ cpu('a', 50, 100, 0),
86
+ cpu('b', 40, 200, 0),
87
+ ]
88
+ const result = podAggregate(containers, 'cpu')
89
+ expect(result.mode).toBe('request')
90
+ expect(result.totalUsage).toBe(90)
91
+ expect(result.denom).toBe(300) // summed request
92
+ expect(result.unlimitedCount).toBe(2)
93
+ })
94
+
95
+ it("reports mode 'none' when no container sets a request or limit", () => {
96
+ const containers = [cpu('a', 10, 0, 0), cpu('b', 20, 0, 0)]
97
+ const result = podAggregate(containers, 'cpu')
98
+ expect(result.mode).toBe('none')
99
+ expect(result.totalUsage).toBe(30)
100
+ expect(result.denom).toBe(0)
101
+ expect(result.unlimitedCount).toBe(2)
102
+ })
103
+
104
+ it('aggregates the memory fields for the memory kind', () => {
105
+ const containers = [mem('a', 64, 32, 128), mem('b', 96, 64, 256)]
106
+ const result = podAggregate(containers, 'memory')
107
+ expect(result.mode).toBe('limit')
108
+ expect(result.totalUsage).toBe(160)
109
+ expect(result.denom).toBe(384) // summed memory limit
110
+ })
111
+ })