@skyhook-io/k8s-ui 1.5.1 → 1.5.3

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.
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Explicit color palettes for the log viewer.
3
+ *
4
+ * The log viewer is intentionally self-contained: it does NOT use theme tokens
5
+ * (`text-theme-*`, `bg-theme-*`, etc.) because those are driven by CSS
6
+ * `light-dark()` and don't resolve correctly through the log viewer's forced
7
+ * `color-scheme` container. Instead it flips between two explicit palettes
8
+ * based on its own `isDark` state (toggled by a Sun/Moon button in the
9
+ * toolbar, persisted to `localStorage['radar-logs-dark']`).
10
+ *
11
+ * All class strings are static literals so Tailwind's class scanner picks them
12
+ * up — do not construct them dynamically.
13
+ */
14
+
15
+ /** All color-related class strings used inside the log viewer. */
16
+ export interface LogPalette {
17
+ // Container / surfaces
18
+ containerBg: string
19
+ toolbarBg: string
20
+ toolbarBgMuted: string
21
+ menuBg: string
22
+ elevatedBg: string
23
+
24
+ // Borders
25
+ border: string
26
+ borderLight: string
27
+
28
+ // Text
29
+ textPrimary: string
30
+ textSecondary: string
31
+ textTertiary: string
32
+ textDisabled: string
33
+ /** Standalone error text (inline "error=..." in structured lines, inline validation errors). */
34
+ textError: string
35
+ /** Accent/link text — blue-family. Used for toolbar toggles, "select all" etc. */
36
+ textAccent: string
37
+
38
+ // Placeholder (plain class, applied via `placeholder-*` below)
39
+ placeholder: string
40
+
41
+ // Hover states
42
+ hoverBg: string
43
+ hoverSurface: string
44
+ hoverText: string
45
+ /** Active/selected toolbar controls inside the viewer. */
46
+ toolbarActive: string
47
+
48
+ // Row highlight (current search match)
49
+ currentMatchBg: string
50
+
51
+ // Level-filter button active colors (per-level, full className)
52
+ levelActiveError: string
53
+ levelActiveWarn: string
54
+ levelActiveInfo: string
55
+ levelActiveDebug: string
56
+
57
+ // Level-badge colors used inside StructuredLogLine
58
+ levelBadgeError: string
59
+ levelBadgeWarn: string
60
+ levelBadgeInfo: string
61
+ levelBadgeDebug: string
62
+ levelBadgeNeutral: string
63
+
64
+ /**
65
+ * Pod label colors for WorkloadLogsViewer (aggregated logs across pods).
66
+ * Pairs: one class for the name text, one for the filter-list dot.
67
+ * Pods are round-robined through this array.
68
+ */
69
+ podColors: Array<{ text: string; bg: string }>
70
+
71
+ /**
72
+ * Syntax-highlight colors for structured JSON/logfmt renderings.
73
+ * Applied as inline `style={{ color }}` (not classes) because the values
74
+ * come from log-format.ts and feed React's style prop.
75
+ */
76
+ syntaxKey: string
77
+ syntaxString: string
78
+ syntaxNumber: string
79
+ syntaxBoolean: string
80
+ syntaxNull: string
81
+ }
82
+
83
+ const DARK_PALETTE: LogPalette = {
84
+ containerBg: 'bg-slate-950',
85
+ toolbarBg: 'bg-slate-900',
86
+ toolbarBgMuted: 'bg-slate-900/60',
87
+ menuBg: 'bg-slate-900',
88
+ elevatedBg: 'bg-slate-800',
89
+
90
+ border: 'border-slate-800',
91
+ borderLight: 'border-slate-700',
92
+
93
+ textPrimary: 'text-slate-100',
94
+ textSecondary: 'text-slate-400',
95
+ textTertiary: 'text-slate-500',
96
+ textDisabled: 'text-slate-600',
97
+ textError: 'text-red-400',
98
+ textAccent: 'text-blue-400',
99
+
100
+ placeholder: 'placeholder-slate-600',
101
+
102
+ hoverBg: 'hover:bg-slate-800',
103
+ hoverSurface: 'hover:bg-slate-800/50',
104
+ hoverText: 'hover:text-slate-100',
105
+ toolbarActive: 'bg-slate-700 text-slate-100 hover:bg-slate-600',
106
+
107
+ currentMatchBg: 'bg-yellow-500/10',
108
+
109
+ levelActiveError: 'bg-red-500/20 text-red-400 border-red-500/40',
110
+ levelActiveWarn: 'bg-amber-500/20 text-amber-400 border-amber-500/40',
111
+ levelActiveInfo: 'bg-blue-500/20 text-blue-400 border-blue-500/40',
112
+ levelActiveDebug: 'bg-slate-700 text-slate-300 border-slate-600',
113
+
114
+ levelBadgeError: 'bg-red-500/20 text-red-400 border border-red-500/40',
115
+ levelBadgeWarn: 'bg-amber-500/20 text-amber-400 border border-amber-500/40',
116
+ levelBadgeInfo: 'bg-blue-500/20 text-blue-400 border border-blue-500/40',
117
+ levelBadgeDebug: 'bg-slate-700 text-slate-300 border border-slate-600',
118
+ levelBadgeNeutral: 'bg-slate-800 text-slate-400 border border-slate-700',
119
+
120
+ podColors: [
121
+ { text: 'text-blue-400', bg: 'bg-blue-400' },
122
+ { text: 'text-emerald-400', bg: 'bg-emerald-400' },
123
+ { text: 'text-amber-400', bg: 'bg-amber-400' },
124
+ { text: 'text-purple-400', bg: 'bg-purple-400' },
125
+ { text: 'text-pink-400', bg: 'bg-pink-400' },
126
+ { text: 'text-cyan-400', bg: 'bg-cyan-400' },
127
+ { text: 'text-orange-400', bg: 'bg-orange-400' },
128
+ { text: 'text-lime-400', bg: 'bg-lime-400' },
129
+ ],
130
+
131
+ // Hex values tuned for the `bg-slate-950` container.
132
+ syntaxKey: '#7cacf8',
133
+ syntaxString: '#73c991',
134
+ syntaxNumber: '#e5c07b',
135
+ syntaxBoolean: '#c678dd',
136
+ syntaxNull: '#808080',
137
+ }
138
+
139
+ const LIGHT_PALETTE: LogPalette = {
140
+ containerBg: 'bg-slate-50',
141
+ toolbarBg: 'bg-slate-100',
142
+ toolbarBgMuted: 'bg-slate-100/60',
143
+ menuBg: 'bg-white',
144
+ elevatedBg: 'bg-white',
145
+
146
+ border: 'border-slate-200',
147
+ borderLight: 'border-slate-300',
148
+
149
+ textPrimary: 'text-slate-900',
150
+ textSecondary: 'text-slate-600',
151
+ textTertiary: 'text-slate-400',
152
+ textDisabled: 'text-slate-300',
153
+ textError: 'text-red-700',
154
+ textAccent: 'text-blue-700',
155
+
156
+ placeholder: 'placeholder-slate-400',
157
+
158
+ hoverBg: 'hover:bg-slate-200',
159
+ hoverSurface: 'hover:bg-slate-200/60',
160
+ hoverText: 'hover:text-slate-900',
161
+ toolbarActive: 'bg-slate-200 text-slate-900 hover:bg-slate-300',
162
+
163
+ currentMatchBg: 'bg-yellow-200/60',
164
+
165
+ levelActiveError: 'bg-red-100 text-red-700 border-red-400',
166
+ levelActiveWarn: 'bg-amber-100 text-amber-700 border-amber-400',
167
+ levelActiveInfo: 'bg-blue-100 text-blue-700 border-blue-400',
168
+ levelActiveDebug: 'bg-slate-200 text-slate-700 border-slate-400',
169
+
170
+ levelBadgeError: 'bg-red-100 text-red-700 border border-red-400',
171
+ levelBadgeWarn: 'bg-amber-100 text-amber-700 border border-amber-400',
172
+ levelBadgeInfo: 'bg-blue-100 text-blue-700 border border-blue-400',
173
+ levelBadgeDebug: 'bg-slate-200 text-slate-700 border border-slate-400',
174
+ levelBadgeNeutral: 'bg-slate-100 text-slate-600 border border-slate-300',
175
+
176
+ podColors: [
177
+ { text: 'text-blue-700', bg: 'bg-blue-700' },
178
+ { text: 'text-emerald-700', bg: 'bg-emerald-700' },
179
+ { text: 'text-amber-700', bg: 'bg-amber-700' },
180
+ { text: 'text-purple-700', bg: 'bg-purple-700' },
181
+ { text: 'text-pink-700', bg: 'bg-pink-700' },
182
+ { text: 'text-cyan-700', bg: 'bg-cyan-700' },
183
+ { text: 'text-orange-700', bg: 'bg-orange-700' },
184
+ { text: 'text-lime-700', bg: 'bg-lime-700' },
185
+ ],
186
+
187
+ // Hex values tuned for the `bg-slate-50` container — darker so they read
188
+ // on a near-white background.
189
+ syntaxKey: '#0b63c0',
190
+ syntaxString: '#2b8a3e',
191
+ syntaxNumber: '#b95f00',
192
+ syntaxBoolean: '#7c3aed',
193
+ syntaxNull: '#6b7280',
194
+ }
195
+
196
+ /** Get the palette for the current dark/light mode. */
197
+ export function getLogPalette(isDark: boolean): LogPalette {
198
+ return isDark ? DARK_PALETTE : LIGHT_PALETTE
199
+ }
200
+
201
+ /** Per-level content color (log text body). */
202
+ export function getLogLevelColor(
203
+ level: 'error' | 'warn' | 'info' | 'debug' | 'unknown',
204
+ isDark: boolean,
205
+ ): string {
206
+ if (isDark) {
207
+ switch (level) {
208
+ case 'error': return 'text-red-400'
209
+ case 'warn': return 'text-amber-400'
210
+ case 'info': return 'text-blue-400'
211
+ case 'debug': return 'text-slate-400'
212
+ default: return 'text-slate-100'
213
+ }
214
+ }
215
+ switch (level) {
216
+ case 'error': return 'text-red-700'
217
+ case 'warn': return 'text-amber-600'
218
+ case 'info': return 'text-blue-700'
219
+ case 'debug': return 'text-slate-500'
220
+ default: return 'text-slate-900'
221
+ }
222
+ }
@@ -9,7 +9,12 @@ export interface LogEntry {
9
9
  content: string
10
10
  container: string
11
11
  pod?: string
12
- podColor?: string
12
+ /**
13
+ * Index into the current palette's `podColors` array. Stored as an index
14
+ * (not a resolved class) so the pod-label color can re-theme when the
15
+ * viewer's isDark state toggles at runtime.
16
+ */
17
+ podColorIndex?: number
13
18
  level: LogLevel
14
19
  isJson: boolean
15
20
  isLogfmt: boolean
@@ -8,10 +8,12 @@ interface UseLogSearchReturn {
8
8
  setQuery: (q: string) => void
9
9
  isRegex: boolean
10
10
  toggleRegex: () => void
11
+ setIsRegex: (v: boolean) => void
11
12
  isCaseSensitive: boolean
12
13
  toggleCaseSensitive: () => void
13
14
  isFilterMode: boolean
14
15
  toggleFilterMode: () => void
16
+ setFilterMode: (v: boolean) => void
15
17
  matchCount: number
16
18
  currentMatch: number
17
19
  /** Indices into the entries array that match */
@@ -121,6 +123,7 @@ export function useLogSearch(
121
123
  const toggleRegex = useCallback(() => setIsRegex(p => !p), [])
122
124
  const toggleCaseSensitive = useCallback(() => setIsCaseSensitive(p => !p), [])
123
125
  const toggleFilterMode = useCallback(() => setIsFilterMode(p => !p), [])
126
+ const setFilterMode = useCallback((v: boolean) => setIsFilterMode(v), [])
124
127
 
125
128
  const open = useCallback(() => setIsOpen(true), [])
126
129
  const close = useCallback(() => {
@@ -133,10 +136,12 @@ export function useLogSearch(
133
136
  setQuery,
134
137
  isRegex,
135
138
  toggleRegex,
139
+ setIsRegex,
136
140
  isCaseSensitive,
137
141
  toggleCaseSensitive,
138
142
  isFilterMode,
139
143
  toggleFilterMode,
144
+ setFilterMode,
140
145
  matchCount: matchIndices.length,
141
146
  currentMatch,
142
147
  matchIndices,
@@ -12,9 +12,16 @@ export function getArgoApplicationStatus(app: any): StatusBadge {
12
12
  const sync = app.status?.sync?.status
13
13
  const opPhase = app.status?.operationState?.phase
14
14
 
15
- // Check for suspended (no automated sync policy)
15
+ // Check for suspended (no automated sync policy). Honor both the current
16
+ // "radarhq.io/suspended-prune" annotation and the legacy "skyhook.io/..."
17
+ // key still present on Applications suspended by older Radar builds.
18
+ // The annotation value stores the prior prune state as "true"/"false" for
19
+ // restore on resume — both strings are truthy in JS, which is intentional:
20
+ // the *presence* of the annotation is what signals suspended, not its value.
16
21
  const hasAutomatedSync = !!app.spec?.syncPolicy?.automated
17
- if (health === 'Suspended' || (!hasAutomatedSync && app.metadata?.annotations?.['radar.skyhook.io/suspended-prune'])) {
22
+ const annotations = app.metadata?.annotations
23
+ const suspendedByRadar = annotations?.['radarhq.io/suspended-prune'] || annotations?.['skyhook.io/suspended-prune']
24
+ if (health === 'Suspended' || (!hasAutomatedSync && suspendedByRadar)) {
18
25
  return { text: 'Suspended', color: healthColors.degraded, level: 'degraded' }
19
26
  }
20
27
 
@@ -5,22 +5,87 @@
5
5
 
6
6
  import type { LogLevel } from '../components/logs/useLogBuffer'
7
7
 
8
+ export type TimestampFormat =
9
+ | 'time-local'
10
+ | 'time-utc'
11
+ | 'iso-local'
12
+ | 'iso-utc'
13
+ | 'relative'
14
+ | 'epoch'
15
+
16
+ export const TIMESTAMP_FORMAT_LABELS: Record<TimestampFormat, string> = {
17
+ 'time-local': 'Time (local)',
18
+ 'time-utc': 'Time (UTC)',
19
+ 'iso-local': 'ISO (local)',
20
+ 'iso-utc': 'ISO (UTC)',
21
+ 'relative': 'Relative',
22
+ 'epoch': 'Epoch',
23
+ }
24
+
25
+ function pad2(n: number): string {
26
+ return n < 10 ? '0' + n : String(n)
27
+ }
28
+
29
+ function formatIsoLocal(date: Date): string {
30
+ const offset = -date.getTimezoneOffset()
31
+ const sign = offset >= 0 ? '+' : '-'
32
+ const abs = Math.abs(offset)
33
+ return (
34
+ date.getFullYear() +
35
+ '-' + pad2(date.getMonth() + 1) +
36
+ '-' + pad2(date.getDate()) +
37
+ 'T' + pad2(date.getHours()) +
38
+ ':' + pad2(date.getMinutes()) +
39
+ ':' + pad2(date.getSeconds()) +
40
+ sign + pad2(Math.floor(abs / 60)) + ':' + pad2(abs % 60)
41
+ )
42
+ }
43
+
44
+ function formatRelative(date: Date, now: number): string {
45
+ const diffSec = Math.round((now - date.getTime()) / 1000)
46
+ const abs = Math.abs(diffSec)
47
+ const suffix = diffSec >= 0 ? ' ago' : ' from now'
48
+ if (abs < 2) return 'just now'
49
+ if (abs < 60) return `${abs}s${suffix}`
50
+ if (abs < 3600) return `${Math.floor(abs / 60)}m${suffix}`
51
+ if (abs < 86400) return `${Math.floor(abs / 3600)}h${suffix}`
52
+ return `${Math.floor(abs / 86400)}d${suffix}`
53
+ }
54
+
8
55
  /**
9
56
  * Format a K8s log timestamp for display.
10
- * Extracts and formats the time portion (HH:MM:SS).
57
+ * Supports multiple display formats and UTC/local time zones.
58
+ * `now` is accepted for testability; defaults to Date.now().
11
59
  */
12
- export function formatLogTimestamp(ts: string): string {
60
+ export function formatLogTimestamp(
61
+ ts: string,
62
+ format: TimestampFormat = 'time-local',
63
+ now?: number,
64
+ ): string {
13
65
  const date = new Date(ts)
14
66
  if (isNaN(date.getTime())) {
15
- // Fallback: extract HH:MM:SS from ISO timestamp
16
67
  return ts.slice(11, 19) || ts
17
68
  }
18
- return date.toLocaleTimeString('en-US', {
19
- hour12: false,
20
- hour: '2-digit',
21
- minute: '2-digit',
22
- second: '2-digit',
23
- })
69
+ switch (format) {
70
+ case 'time-utc':
71
+ return date.toISOString().slice(11, 19)
72
+ case 'iso-local':
73
+ return formatIsoLocal(date)
74
+ case 'iso-utc':
75
+ return date.toISOString()
76
+ case 'relative':
77
+ return formatRelative(date, now ?? Date.now())
78
+ case 'epoch':
79
+ return String(Math.floor(date.getTime() / 1000))
80
+ case 'time-local':
81
+ default:
82
+ return date.toLocaleTimeString('en-US', {
83
+ hour12: false,
84
+ hour: '2-digit',
85
+ minute: '2-digit',
86
+ second: '2-digit',
87
+ })
88
+ }
24
89
  }
25
90
 
26
91
  /** Map a detected LogLevel to a Tailwind color class. */
@@ -294,9 +359,9 @@ export function parseLogRange(logRange: string): { tailLines?: number; sinceSeco
294
359
  // Syntax highlight colors shared between JSON and logfmt rendering
295
360
  export const SYNTAX_COLOR_KEY = '#7cacf8'
296
361
  export const SYNTAX_COLOR_STRING = '#73c991'
297
- const SYNTAX_COLOR_NUMBER = '#e5c07b'
298
- const SYNTAX_COLOR_BOOLEAN = '#c678dd'
299
- const SYNTAX_COLOR_NULL = '#808080'
362
+ export const SYNTAX_COLOR_NUMBER = '#e5c07b'
363
+ export const SYNTAX_COLOR_BOOLEAN = '#c678dd'
364
+ export const SYNTAX_COLOR_NULL = '#808080'
300
365
 
301
366
  /**
302
367
  * Syntax-highlight a pretty-printed JSON string for HTML display.