Skip to content

Commit 07b91a1

Browse files
committed
fix(containers): address review feedback on Info tab
- Reorder v-if/v-else-if/v-else so error/unknown state is the final fallback; use `{{ error || 'An unknown error has occurred' }}` - Wrap Name and Image summary fields in <code> for consistent styling - Replace .section-summary class with nested > summary selector in .inspect-section, removing the redundant class from all <summary> elements - Move ContainerInspectData fetch into the container-engine store: export ContainerMount/ContainerInspectData interfaces, add inspectData state, SET_INSPECT_DATA mutation, and fetchContainerInspect action; clear inspectData on backend/namespace change; component now dispatches the action and reads data from store - Simplify e2e nav helper usage (navPage.navigateTo returns the page) Signed-off-by: Gildas Le Nadan <3ntr0p13+github@gmail.com>
1 parent ebbf42a commit 07b91a1

File tree

3 files changed

+126
-111
lines changed

3 files changed

+126
-111
lines changed

e2e/containers.e2e.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,7 @@ test.describe.serial('Container Info Tab', () => {
440440
);
441441
infoContainerId = output.trim();
442442

443-
const navPage2 = new NavPage(page);
444-
await navPage2.navigateTo('Containers');
445-
const containersPage = new ContainersPage(page);
443+
const containersPage = await navPage.navigateTo('Containers');
446444
await containersPage.waitForTableToLoad();
447445
await containersPage.waitForContainerToAppear(infoContainerId);
448446
await containersPage.clickContainerAction(infoContainerId, 'info');

pkg/rancher-desktop/components/ContainerInspect.vue

Lines changed: 53 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,6 @@
1212
Loading container details…
1313
</div>
1414

15-
<div
16-
v-else-if="error"
17-
class="error-state"
18-
data-testid="info-error"
19-
>
20-
<i class="icon icon-warning" />
21-
{{ error }}
22-
</div>
23-
2415
<template v-else-if="data">
2516
<!-- Summary table -->
2617
<table
@@ -30,15 +21,15 @@
3021
<tbody>
3122
<tr data-testid="info-row-name">
3223
<th>Name</th>
33-
<td>{{ displayName }}</td>
24+
<td><code>{{ displayName }}</code></td>
3425
</tr>
3526
<tr data-testid="info-row-id">
3627
<th>ID</th>
3728
<td><code>{{ shortId }}</code></td>
3829
</tr>
3930
<tr data-testid="info-row-image">
4031
<th>Image</th>
41-
<td>{{ data.Config.Image }}</td>
32+
<td><code>{{ data.Config.Image }}</code></td>
4233
</tr>
4334
<tr data-testid="info-row-ip">
4435
<th>IP Address</th>
@@ -63,7 +54,7 @@
6354
class="inspect-section"
6455
data-testid="info-section-mounts"
6556
>
66-
<summary class="section-summary">
57+
<summary>
6758
Mounts <span class="count">({{ data.Mounts.length }})</span>
6859
</summary>
6960
<div
@@ -105,7 +96,7 @@
10596
class="inspect-section"
10697
data-testid="info-section-env"
10798
>
108-
<summary class="section-summary">
99+
<summary>
109100
Environment <span class="count">({{ envVars.length }})</span>
110101
</summary>
111102
<div
@@ -127,7 +118,7 @@
127118
class="inspect-section"
128119
data-testid="info-section-command"
129120
>
130-
<summary class="section-summary">
121+
<summary>
131122
Command &amp; Args
132123
</summary>
133124
<div class="section-body">
@@ -155,7 +146,7 @@
155146
class="inspect-section"
156147
data-testid="info-section-capabilities"
157148
>
158-
<summary class="section-summary">
149+
<summary>
159150
Capabilities
160151
</summary>
161152
<div class="section-body">
@@ -179,7 +170,7 @@
179170
class="inspect-section"
180171
data-testid="info-section-ports"
181172
>
182-
<summary class="section-summary">
173+
<summary>
183174
Ports <span class="count">({{ portEntries.length }})</span>
184175
</summary>
185176
<div
@@ -208,7 +199,7 @@
208199
class="inspect-section"
209200
data-testid="info-section-labels"
210201
>
211-
<summary class="section-summary">
202+
<summary>
212203
Labels <span class="count">({{ labelEntries.length }})</span>
213204
</summary>
214205
<div
@@ -235,55 +226,33 @@
235226
</div>
236227
</details>
237228
</template>
229+
230+
<div
231+
v-else
232+
class="error-state"
233+
data-testid="info-error"
234+
>
235+
<i class="icon icon-warning" />
236+
{{ error || 'An unknown error has occurred' }}
237+
</div>
238238
</div>
239239
</template>
240240

241241
<script setup lang="ts">
242242
import { ref, computed, watch, onMounted } from 'vue';
243+
import { useStore } from 'vuex';
243244
244-
interface ContainerMount {
245-
Type: string;
246-
Source: string;
247-
Destination: string;
248-
RW: boolean;
249-
Mode: string;
250-
}
251-
252-
interface ContainerInspectData {
253-
Id: string;
254-
Name: string;
255-
Created: string;
256-
State: {
257-
Status: string;
258-
StartedAt: string;
259-
FinishedAt: string;
260-
};
261-
Config: {
262-
Image: string;
263-
Env: string[] | null;
264-
Cmd: string[] | null;
265-
Entrypoint: string[] | null;
266-
Labels: Record<string, string> | null;
267-
};
268-
HostConfig: {
269-
CapAdd: string[] | null;
270-
CapDrop: string[] | null;
271-
};
272-
Mounts: ContainerMount[];
273-
NetworkSettings: {
274-
IPAddress: string;
275-
Ports: Record<string, ({ HostIp: string; HostPort: string }[]) | null>;
276-
Networks: Record<string, { IPAddress: string }>;
277-
};
278-
Args: string[];
279-
}
245+
import type { ContainerInspectData } from '@pkg/store/container-engine';
280246
281247
const props = defineProps<{
282248
containerId: string;
283249
namespace: string | undefined;
284250
}>();
285251
286-
const data = ref<ContainerInspectData | null>(null);
252+
const store = useStore();
253+
const data = computed<ContainerInspectData | undefined>(
254+
() => store.state['container-engine'].inspectData[props.containerId],
255+
);
287256
const loading = ref(false);
288257
const error = ref<string | null>(null);
289258
@@ -293,17 +262,12 @@ const fetchInspect = async() => {
293262
}
294263
loading.value = true;
295264
error.value = null;
296-
data.value = null;
297265
298266
try {
299-
const result = await window.ddClient.docker.cli.exec(
300-
'inspect',
301-
[props.containerId],
302-
{ namespace: props.namespace },
303-
);
304-
const parsed = result.parseJsonObject() as ContainerInspectData[];
305-
306-
data.value = parsed[0];
267+
await store.dispatch('container-engine/fetchContainerInspect', {
268+
containerId: props.containerId,
269+
namespace: props.namespace,
270+
});
307271
} catch (err: any) {
308272
error.value = err?.message ?? 'Failed to load container details.';
309273
} finally {
@@ -413,38 +377,38 @@ const formatDate = (iso: string): string => {
413377
border-radius: var(--border-radius);
414378
overflow: hidden;
415379
416-
&[open] .section-summary {
380+
&[open] > summary {
417381
border-bottom: 1px solid var(--border);
418-
}
419-
}
420-
421-
.section-summary {
422-
padding: 0.5rem 0.75rem;
423-
cursor: pointer;
424-
user-select: none;
425-
font-weight: 500;
426-
list-style: none;
427382
428-
&::-webkit-details-marker {
429-
display: none;
383+
&::before {
384+
transform: rotate(90deg);
385+
}
430386
}
431387
432-
&::before {
433-
content: '';
434-
display: inline-block;
435-
margin-right: 0.5rem;
436-
transition: transform 0.15s ease;
437-
font-size: 0.7em;
438-
}
388+
> summary {
389+
padding: 0.5rem 0.75rem;
390+
cursor: pointer;
391+
user-select: none;
392+
font-weight: 500;
393+
list-style: none;
439394
440-
details[open] &::before {
441-
transform: rotate(90deg);
442-
}
395+
&::-webkit-details-marker {
396+
display: none;
397+
}
443398
444-
.count {
445-
color: var(--muted);
446-
font-weight: 400;
447-
font-size: 0.875em;
399+
&::before {
400+
content: '';
401+
display: inline-block;
402+
margin-right: 0.5rem;
403+
transition: transform 0.15s ease;
404+
font-size: 0.7em;
405+
}
406+
407+
.count {
408+
color: var(--muted);
409+
font-weight: 400;
410+
font-size: 0.875em;
411+
}
448412
}
449413
}
450414

pkg/rancher-desktop/store/container-engine.ts

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,44 @@ import type { RDXClient } from '@pkg/preload/extensions';
88

99
type ValidContainerEngine = Exclude<ContainerEngine, ContainerEngine.NONE>;
1010
type SubscriberType = 'containers' | 'volumes';
11-
type ErrorSource = 'containers' | 'volumes' | 'namespaces';
11+
type ErrorSource = 'containers' | 'volumes' | 'namespaces' | 'inspect';
12+
13+
export interface ContainerMount {
14+
Type: string;
15+
Source: string;
16+
Destination: string;
17+
RW: boolean;
18+
Mode: string;
19+
}
20+
21+
export interface ContainerInspectData {
22+
Id: string;
23+
Name: string;
24+
Created: string;
25+
State: {
26+
Status: string;
27+
StartedAt: string;
28+
FinishedAt: string;
29+
};
30+
Config: {
31+
Image: string;
32+
Env: string[] | null;
33+
Cmd: string[] | null;
34+
Entrypoint: string[] | null;
35+
Labels: Record<string, string> | null;
36+
};
37+
HostConfig: {
38+
CapAdd: string[] | null;
39+
CapDrop: string[] | null;
40+
};
41+
Mounts: ContainerMount[];
42+
NetworkSettings: {
43+
IPAddress: string;
44+
Ports: Record<string, ({ HostIp: string; HostPort: string }[]) | null>;
45+
Networks: Record<string, { IPAddress: string }>;
46+
};
47+
Args: string[];
48+
}
1249

1350
/**
1451
* Shared extension API container list result parts
@@ -179,29 +216,31 @@ function subscriberConstructor(backend: ValidContainerEngine, type: SubscriberTy
179216

180217
export interface ContainersState {
181218
/** The backend in use; this may not match the committed preferences. */
182-
backend: ContainerEngine;
219+
backend: ContainerEngine;
183220
/** The type of object to monitor. */
184-
type: SubscriberType;
185-
client: RDXClient | null;
186-
namespaces: string[] | null;
187-
namespace: string | undefined;
188-
subscriber: Subscriber | null;
189-
containers: Record<string, Container> | null;
190-
volumes: Record<string, Volume> | null;
221+
type: SubscriberType;
222+
client: RDXClient | null;
223+
namespaces: string[] | null;
224+
namespace: string | undefined;
225+
subscriber: Subscriber | null;
226+
containers: Record<string, Container> | null;
227+
volumes: Record<string, Volume> | null;
228+
inspectData: Record<string, ContainerInspectData>;
191229
/** The last error encountered, plus which fetch caused it. */
192-
error: { source: ErrorSource, error: Error } | null;
230+
error: { source: ErrorSource, error: Error } | null;
193231
}
194232

195233
export const state: () => ContainersState = () => ({
196-
backend: ContainerEngine.NONE,
197-
type: 'containers',
198-
client: null,
199-
namespaces: null,
200-
namespace: undefined,
201-
subscriber: null,
202-
containers: null,
203-
volumes: null,
204-
error: null,
234+
backend: ContainerEngine.NONE,
235+
type: 'containers',
236+
client: null,
237+
namespaces: null,
238+
namespace: undefined,
239+
subscriber: null,
240+
containers: null,
241+
volumes: null,
242+
inspectData: {},
243+
error: null,
205244
});
206245

207246
type BulkParams = Pick<ContainersState, 'backend' | 'type' | 'client' | 'namespace'>;
@@ -223,6 +262,9 @@ export const mutations = {
223262
SET_ERROR(state, error) {
224263
state.error = error;
225264
},
265+
SET_INSPECT_DATA(state, { containerId, data }: { containerId: string, data: ContainerInspectData }) {
266+
state.inspectData = { ...state.inspectData, [containerId]: data };
267+
},
226268
SET_PARAMS(state, params: BulkParams) {
227269
let clearData = false;
228270
switch (true) {
@@ -242,6 +284,7 @@ export const mutations = {
242284
state.subscriber = null;
243285
state.containers = null;
244286
state.volumes = null;
287+
state.inspectData = {};
245288
}
246289
},
247290
} satisfies Partial<MutationsType<ContainersState>> & MutationTree<ContainersState>;
@@ -351,6 +394,16 @@ export const actions = {
351394
commit('SET_ERROR', { source: 'containers', error });
352395
}
353396
},
397+
async fetchContainerInspect({ commit, state }, { containerId, namespace }: { containerId: string, namespace?: string }) {
398+
const result = await state.client?.docker.cli.exec(
399+
'inspect',
400+
[containerId],
401+
{ namespace },
402+
);
403+
const parsed = result.parseJsonObject() as ContainerInspectData[];
404+
405+
commit('SET_INSPECT_DATA', { containerId, data: parsed[0] });
406+
},
354407
async fetchVolumes({ commit, getters, state }) {
355408
try {
356409
const { client, namespace } = state;

0 commit comments

Comments
 (0)