openk8s 1.0.2 → 1.0.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.
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ openk8s is a keyboard-driven TUI for browsing and managing Kubernetes clusters,
|
|
|
12
12
|
|
|
13
13
|
| Tool | Required | Notes |
|
|
14
14
|
|------|----------|-------|
|
|
15
|
-
| [kubectl](https://kubernetes.io/docs/tasks/tools/) | yes |
|
|
15
|
+
| [kubectl](https://kubernetes.io/docs/tasks/tools/) | yes | >= 1.36.1, must be configured with a valid kubeconfig |
|
|
16
16
|
| [helm](https://helm.sh/) | no | Enables HelmRelease management |
|
|
17
17
|
| [bun](https://bun.sh/) | yes | Required at runtime to execute the app |
|
|
18
18
|
|
package/package.json
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
defaultNamespace,
|
|
19
19
|
selectedRef,
|
|
20
20
|
selectedForwardsForRef,
|
|
21
|
+
truncate,
|
|
22
|
+
formatClusterName,
|
|
21
23
|
} from "../utils";
|
|
22
24
|
import type { ResourceListItem, ActivePortForward, ResourceDetail, NamespaceItem, ResourceKind } from "../../lib/k8s/types";
|
|
23
25
|
|
|
@@ -333,6 +335,58 @@ describe("selectedRef", () => {
|
|
|
333
335
|
});
|
|
334
336
|
});
|
|
335
337
|
|
|
338
|
+
// ── truncate ──────────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
describe("truncate", () => {
|
|
341
|
+
test("returns string as-is when within limit", () => {
|
|
342
|
+
expect(truncate("hello", 10)).toBe("hello");
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("truncates with ellipsis when over limit", () => {
|
|
346
|
+
expect(truncate("hello world", 5)).toBe("hell\u2026");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("handles empty string", () => {
|
|
350
|
+
expect(truncate("", 5)).toBe("");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("handles exact match", () => {
|
|
354
|
+
expect(truncate("hello", 5)).toBe("hello");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("handles single-char limit", () => {
|
|
358
|
+
expect(truncate("ab", 1)).toBe("\u2026");
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ── formatClusterName ─────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
describe("formatClusterName", () => {
|
|
365
|
+
test("formats EKS ARN", () => {
|
|
366
|
+
expect(formatClusterName("arn:aws:eks:us-east-1:123456789012:cluster/my-cluster", 30)).toBe("aws/eks/my-cluster");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("truncates long EKS cluster name", () => {
|
|
370
|
+
expect(formatClusterName("arn:aws:eks:us-east-1:123456789012:cluster/this-name-is-way-too-long", 20)).toBe("aws/eks/this-name-i\u2026");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("passes through non-ARN names", () => {
|
|
374
|
+
expect(formatClusterName("minikube", 30)).toBe("minikube");
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("truncates long non-ARN names", () => {
|
|
378
|
+
expect(formatClusterName("this-context-name-is-very-long", 15)).toBe("this-context-n\u2026");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("handles empty string", () => {
|
|
382
|
+
expect(formatClusterName("", 10)).toBe("");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("handles EKS name with hyphens and dots", () => {
|
|
386
|
+
expect(formatClusterName("arn:aws:eks:eu-west-2:123456789012:cluster/prod.blue-123", 30)).toBe("aws/eks/prod.blue-123");
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
336
390
|
// ── selectedForwardsForRef ──────────────────────────────────────────────────
|
|
337
391
|
|
|
338
392
|
describe("selectedForwardsForRef", () => {
|
package/src/app/app-state.ts
CHANGED
|
@@ -94,7 +94,18 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
|
|
94
94
|
selectedKind: action.selectedKind,
|
|
95
95
|
};
|
|
96
96
|
case "setSelectedKind":
|
|
97
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
...state,
|
|
99
|
+
selectedKind: action.kind,
|
|
100
|
+
resourceFilter: "",
|
|
101
|
+
selectedResourceNames: [],
|
|
102
|
+
resources: [],
|
|
103
|
+
resourcesStatus: "loading",
|
|
104
|
+
selectedResourceDetail: undefined,
|
|
105
|
+
selectedResourceDetailStatus: "idle",
|
|
106
|
+
events: [],
|
|
107
|
+
eventsStatus: "idle",
|
|
108
|
+
};
|
|
98
109
|
case "setResourcesStatus":
|
|
99
110
|
return { ...state, resourcesStatus: action.status };
|
|
100
111
|
case "setResources":
|
package/src/app/app.tsx
CHANGED
|
@@ -22,11 +22,13 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
currentKindLabel,
|
|
24
24
|
filterResources,
|
|
25
|
+
formatClusterName,
|
|
25
26
|
nextVisibleResourceName,
|
|
26
27
|
resourcePreview,
|
|
27
28
|
selectedForwardsForRef,
|
|
28
29
|
selectedRef,
|
|
29
30
|
statusLine,
|
|
31
|
+
truncate,
|
|
30
32
|
} from "./utils";
|
|
31
33
|
import { usePollingTick } from "./use-polling-tick";
|
|
32
34
|
import { useFooterHints, useFooterHintRows } from "./use-footer-hints";
|
|
@@ -365,14 +367,18 @@ export function App() {
|
|
|
365
367
|
padding: 1,
|
|
366
368
|
}}
|
|
367
369
|
>
|
|
368
|
-
<
|
|
369
|
-
<
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
370
|
+
<box style={{ width: "100%" }}>
|
|
371
|
+
<text fg={TEXT_PRIMARY}>
|
|
372
|
+
<span fg={KEY_HINT}>{GLYPHS.cluster}</span>
|
|
373
|
+
{` ${formatClusterName(state.activeContext ?? "none", 26)}`}
|
|
374
|
+
</text>
|
|
375
|
+
</box>
|
|
376
|
+
<box style={{ width: "100%" }}>
|
|
377
|
+
<text fg={TEXT_SUBTLE} attributes={TEXT_MUTED}>
|
|
378
|
+
<span fg={KEY_HINT}>{GLYPHS.ns}</span>
|
|
379
|
+
{` ${truncate(state.activeNamespace, 26)}`}
|
|
380
|
+
</text>
|
|
381
|
+
</box>
|
|
376
382
|
<KindRows
|
|
377
383
|
resourceKinds={state.resourceKinds}
|
|
378
384
|
selectedKind={state.selectedKind}
|
|
@@ -43,17 +43,16 @@ export function ResourceRows({
|
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
45
|
<scrollbox ref={scrollRef} onMouseDown={onActivate} style={{ flexGrow: 1 }}>
|
|
46
|
-
{
|
|
46
|
+
{status === "loading" ? (
|
|
47
|
+
<text fg={TEXT_SUBTLE} attributes={TextAttributes.DIM}>
|
|
48
|
+
{"\u27F3 Refreshing..."}
|
|
49
|
+
</text>
|
|
50
|
+
) : resources.length === 0 ? (
|
|
47
51
|
<text fg={TEXT_SUBTLE} attributes={TextAttributes.DIM}>
|
|
48
52
|
{resourceEmptyState(status, filter)}
|
|
49
53
|
</text>
|
|
50
54
|
) : (
|
|
51
55
|
<box style={{ flexDirection: "column", width: "100%" }}>
|
|
52
|
-
{status === "loading" ? (
|
|
53
|
-
<text fg={TEXT_SUBTLE} attributes={TextAttributes.DIM}>
|
|
54
|
-
{"\u27F3 Refreshing..."}
|
|
55
|
-
</text>
|
|
56
|
-
) : null}
|
|
57
56
|
{resources.map((resource) => {
|
|
58
57
|
const selected = resource.ref.name === selectedName;
|
|
59
58
|
const marked = selectedNames.includes(resource.ref.name);
|
|
@@ -166,8 +166,6 @@ export function useDataFetching(
|
|
|
166
166
|
state.kubectlAvailable,
|
|
167
167
|
clusterPollTick,
|
|
168
168
|
manualRefreshNonce,
|
|
169
|
-
state.activeNamespace,
|
|
170
|
-
state.selectedKind,
|
|
171
169
|
]);
|
|
172
170
|
|
|
173
171
|
// Load the resource list. Bug-fix: removed state.resources.length and state.selectedResourceName
|
package/src/app/utils.ts
CHANGED
|
@@ -260,6 +260,22 @@ export function selectedRef(options: SelectedRefOptions): ResourceRef | undefine
|
|
|
260
260
|
return options.resource?.ref;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
const EKS_ARN_RE = /^arn:aws:eks:[^:]+:\d{12}:cluster\/(.+)$/;
|
|
264
|
+
|
|
265
|
+
export function truncate(s: string, maxLen: number): string {
|
|
266
|
+
if (s.length <= maxLen) return s;
|
|
267
|
+
return s.slice(0, maxLen - 1) + "\u2026";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function formatClusterName(name: string, maxLen: number): string {
|
|
271
|
+
const match = name.match(EKS_ARN_RE);
|
|
272
|
+
if (match) {
|
|
273
|
+
const label = `aws/eks/${match[1]}`;
|
|
274
|
+
return truncate(label, maxLen);
|
|
275
|
+
}
|
|
276
|
+
return truncate(name, maxLen);
|
|
277
|
+
}
|
|
278
|
+
|
|
263
279
|
export function selectedForwardsForRef(options: SelectedForwardsForRefOptions): ActivePortForward[] {
|
|
264
280
|
if (!options.ref) {
|
|
265
281
|
return [];
|