From d7b9f20c79420091228c930dafcd21a48a070e36 Mon Sep 17 00:00:00 2001 From: Lars Bergmann Date: Thu, 23 Jul 2026 14:01:46 +0200 Subject: [PATCH 1/5] Enrich context getter to provide project details --- src/commands/context/get.tsx | 342 ++++++++++++++++++++++++++++++++++- 1 file changed, 338 insertions(+), 4 deletions(-) diff --git a/src/commands/context/get.tsx b/src/commands/context/get.tsx index e8000ffee..9160a89fb 100644 --- a/src/commands/context/get.tsx +++ b/src/commands/context/get.tsx @@ -5,15 +5,62 @@ import { Value } from "../../rendering/react/components/Value.js"; import { usePromise } from "@mittwald/react-use-promise"; import { Note } from "../../rendering/react/components/Note.js"; import { Box, Text } from "ink"; -import { Set } from "./set.js"; +import { Set as SetCommand } from "./set.js"; import { RenderJson } from "../../rendering/react/json/RenderJson.js"; import { useRenderContext } from "../../rendering/react/context.js"; import { LocalFilename } from "../../rendering/react/components/LocalFilename.js"; +import { MittwaldAPIV2 } from "@mittwald/api-client"; +import { assertStatus } from "@mittwald/api-client-commons"; import Context, { ContextKey, ContextValue, ContextValueSource, } from "../../lib/context/Context.js"; +import { + getAppFromUuid, + getAppInstallationFromUuid, +} from "../../lib/resources/app/uuid.js"; + +type AppLinkedDatabase = MittwaldAPIV2.Components.Schemas.AppLinkedDatabase; + +type LinkedDatabaseSummary = { + databaseId: string; + purpose: string; + kind: "mysql" | "redis" | "unknown"; + name?: string; +}; + +type AppSummary = { + installationId: string; + appId: string; + appName: string; + installationPath: string; + linkedDatabases: LinkedDatabaseSummary[]; +}; + +type StackSummary = { + id: string; + description?: string; + services: number; + volumes: number; +}; + +type ContainerSummary = { + id: string; + name: string; + status: string; + stackId?: string; +}; + +type ProjectOverview = { + projectId?: string; + projectName?: string; + resolvedFrom?: "project-id" | "installation-id"; + apps: AppSummary[]; + stacks: StackSummary[]; + containers: ContainerSummary[]; + unavailableReason?: string; +}; const ContextSourceValue: FC<{ source: ContextValueSource }> = ({ source }) => { switch (source.type) { @@ -75,9 +122,90 @@ const ContextSource: FC<{ source: ContextValueSource }> = ({ source }) => { ); }; +const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ + overview, +}) => { + if (overview.unavailableReason) { + return ( + + Project overview is unavailable: {overview.unavailableReason} + + ); + } + + const rows: Record = { + Project: ( + + {overview.projectName ?? overview.projectId}{" "} + ({overview.projectId}) + + ), + "Resolved from": {overview.resolvedFrom ?? "project-id"}, + }; + + if (overview.apps.length > 0) { + rows["Apps"] = ( + + {overview.apps.map((app) => ( + + + {app.appName}{" "} + + ({app.installationPath}, {app.installationId}) + + + {app.linkedDatabases.length > 0 ? ( + app.linkedDatabases.map((db) => ( + + database {db.purpose}: {db.name ?? db.databaseId} ({db.kind}) + + )) + ) : ( + no linked databases + )} + + ))} + + ); + } else { + rows["Apps"] = none found in this project; + rows["Stacks"] = ( + + + {overview.stacks.length} total + + {overview.stacks.slice(0, 5).map((stack) => ( + + {stack.id}: {stack.services} services, {stack.volumes} volumes + {stack.description ? ` (${stack.description})` : ""} + + ))} + + ); + rows["Containers"] = ( + + + {overview.containers.length} total + + {overview.containers.slice(0, 8).map((container) => ( + + {container.name}: {container.status} + {container.stackId ? ` (stack ${container.stackId})` : ""} + + ))} + + ); + } + + return ; +}; + const GetContext: FC<{ ctx: Context }> = ({ ctx }) => { const rows: Record = {}; - const { renderAsJson } = useRenderContext(); + const { renderAsJson, apiClient } = useRenderContext(); const values: Record = {}; let hasTerraformSource = false; @@ -109,8 +237,211 @@ const GetContext: FC<{ ctx: Context }> = ({ ctx }) => { } } + const projectIdFromContext = values["project-id"]?.value; + const appInstallationId = values["installation-id"]?.value; + + const resolvedProject = usePromise( + async ( + contextProjectId: string | undefined, + installationId: string | undefined, + ): Promise<{ + projectId?: string; + resolvedFrom?: "project-id" | "installation-id"; + unavailableReason?: string; + }> => { + if (contextProjectId) { + return { projectId: contextProjectId, resolvedFrom: "project-id" }; + } + + if (!installationId) { + return { + unavailableReason: + "no project-id in context and no installation-id to derive it from", + }; + } + + try { + const installation = await getAppInstallationFromUuid( + apiClient, + installationId, + ); + return { + projectId: installation.projectId, + resolvedFrom: "installation-id", + }; + } catch { + return { + unavailableReason: "could not resolve project from installation-id", + }; + } + }, + [projectIdFromContext, appInstallationId], + ); + + const overview = usePromise( + async ( + projectId: string | undefined, + resolvedFrom: "project-id" | "installation-id" | undefined, + unavailableReason: string | undefined, + ): Promise => { + if (!projectId) { + return { + apps: [], + stacks: [], + containers: [], + unavailableReason: + unavailableReason ?? "project could not be resolved", + }; + } + + try { + const projectResponse = await apiClient.project.getProject({ + projectId, + }); + assertStatus(projectResponse, 200); + + const appInstallationsResponse = + await apiClient.app.listAppinstallations({ projectId }); + assertStatus(appInstallationsResponse, 200); + + const appInstallations = appInstallationsResponse.data; + const uniqueAppIds = Array.from( + new Set(appInstallations.map((installation) => installation.appId)), + ); + + const appNames = new Map(); + await Promise.all( + uniqueAppIds.map(async (appId) => { + try { + const app = await getAppFromUuid(apiClient, appId); + appNames.set(appId, app.name); + } catch { + appNames.set(appId, appId); + } + }), + ); + + const databaseById = new Map< + string, + { name: string; kind: "mysql" | "redis" } + >(); + + try { + const mysqlResponse = await apiClient.database.listMysqlDatabases({ + projectId, + }); + assertStatus(mysqlResponse, 200); + for (const db of mysqlResponse.data) { + databaseById.set(db.id, { name: db.name, kind: "mysql" }); + } + } catch { + // best effort + } + + try { + const redisResponse = await apiClient.database.listRedisDatabases({ + projectId, + }); + assertStatus(redisResponse, 200); + for (const db of redisResponse.data) { + databaseById.set(db.id, { name: db.name, kind: "redis" }); + } + } catch { + // best effort + } + + const apps: AppSummary[] = appInstallations.map((installation) => { + const linkedDatabases: LinkedDatabaseSummary[] = + installation.linkedDatabases.map((linked: AppLinkedDatabase) => { + const resolved = databaseById.get(linked.databaseId); + return { + databaseId: linked.databaseId, + purpose: linked.purpose, + kind: resolved?.kind ?? "unknown", + name: resolved?.name, + }; + }); + + return { + installationId: installation.id, + appId: installation.appId, + appName: appNames.get(installation.appId) ?? installation.appId, + installationPath: installation.installationPath, + linkedDatabases, + }; + }); + + if (apps.length > 0) { + return { + projectId, + projectName: projectResponse.data.description, + resolvedFrom, + apps, + stacks: [], + containers: [], + }; + } + + const stackResponse = await apiClient.container.listStacks({ + projectId, + }); + assertStatus(stackResponse, 200); + + const serviceResponse = await apiClient.container.listServices({ + projectId, + }); + assertStatus(serviceResponse, 200); + + const stacks: StackSummary[] = stackResponse.data.map((stack) => ({ + id: stack.id, + description: stack.description, + services: stack.services?.length ?? 0, + volumes: stack.volumes?.length ?? 0, + })); + + const containers: ContainerSummary[] = serviceResponse.data.map( + (service) => ({ + id: service.id, + name: service.serviceName, + status: service.status, + stackId: service.stackId, + }), + ); + + return { + projectId, + projectName: projectResponse.data.description, + resolvedFrom, + apps, + stacks, + containers, + }; + } catch { + return { + projectId, + resolvedFrom, + apps: [], + stacks: [], + containers: [], + unavailableReason: + "project-level data could not be fetched with current access/context", + }; + } + }, + [ + resolvedProject.projectId, + resolvedProject.resolvedFrom, + resolvedProject.unavailableReason, + ], + ); + if (renderAsJson) { - return ; + return ( + <> + + + + ); } return ( @@ -118,6 +449,9 @@ const GetContext: FC<{ ctx: Context }> = ({ ctx }) => { + + + {hasTerraformSource && } {hasDDEVSource && } {hasDotfileSource && } @@ -156,7 +490,7 @@ const ContextSetHint: FC = () => ( export class Get extends RenderBaseCommand { static summary = "Print an overview of currently set context parameters"; - static description = Set.description; + static description = SetCommand.description; static flags = { ...RenderBaseCommand.buildFlags() }; protected render(): ReactNode { From b28fa996921494f491e9f73c88e4a0e42abe7aff Mon Sep 17 00:00:00 2001 From: Lars Bergmann Date: Mon, 27 Jul 2026 08:39:08 +0200 Subject: [PATCH 2/5] Separate prject details module, show short IDs, both apps and container --- src/commands/context/get.tsx | 289 +++++------------------------ src/lib/context/projectOverview.ts | 235 +++++++++++++++++++++++ 2 files changed, 280 insertions(+), 244 deletions(-) create mode 100644 src/lib/context/projectOverview.ts diff --git a/src/commands/context/get.tsx b/src/commands/context/get.tsx index 9160a89fb..72026890d 100644 --- a/src/commands/context/get.tsx +++ b/src/commands/context/get.tsx @@ -9,58 +9,16 @@ import { Set as SetCommand } from "./set.js"; import { RenderJson } from "../../rendering/react/json/RenderJson.js"; import { useRenderContext } from "../../rendering/react/context.js"; import { LocalFilename } from "../../rendering/react/components/LocalFilename.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { assertStatus } from "@mittwald/api-client-commons"; import Context, { ContextKey, ContextValue, ContextValueSource, } from "../../lib/context/Context.js"; import { - getAppFromUuid, - getAppInstallationFromUuid, -} from "../../lib/resources/app/uuid.js"; - -type AppLinkedDatabase = MittwaldAPIV2.Components.Schemas.AppLinkedDatabase; - -type LinkedDatabaseSummary = { - databaseId: string; - purpose: string; - kind: "mysql" | "redis" | "unknown"; - name?: string; -}; - -type AppSummary = { - installationId: string; - appId: string; - appName: string; - installationPath: string; - linkedDatabases: LinkedDatabaseSummary[]; -}; - -type StackSummary = { - id: string; - description?: string; - services: number; - volumes: number; -}; - -type ContainerSummary = { - id: string; - name: string; - status: string; - stackId?: string; -}; - -type ProjectOverview = { - projectId?: string; - projectName?: string; - resolvedFrom?: "project-id" | "installation-id"; - apps: AppSummary[]; - stacks: StackSummary[]; - containers: ContainerSummary[]; - unavailableReason?: string; -}; + fetchProjectOverview, + ProjectOverview, + resolveProjectContext, +} from "../../lib/context/projectOverview.js"; const ContextSourceValue: FC<{ source: ContextValueSource }> = ({ source }) => { switch (source.type) { @@ -125,6 +83,10 @@ const ContextSource: FC<{ source: ContextValueSource }> = ({ source }) => { const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ overview, }) => { + const stackDisplayById = new Map( + overview.stacks.map((stack) => [stack.id, stack.shortId ?? stack.id]), + ); + if (overview.unavailableReason) { return ( @@ -137,21 +99,24 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ Project: ( {overview.projectName ?? overview.projectId}{" "} - ({overview.projectId}) + + ({overview.projectShortId ?? overview.projectId}) + ), "Resolved from": {overview.resolvedFrom ?? "project-id"}, }; - if (overview.apps.length > 0) { - rows["Apps"] = ( + rows["Apps"] = + overview.apps.length > 0 ? ( {overview.apps.map((app) => ( {app.appName}{" "} - ({app.installationPath}, {app.installationId}) + ({app.installationPath},{" "} + {app.installationShortId ?? app.installationId}) {app.linkedDatabases.length > 0 ? ( @@ -169,36 +134,49 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ ))} + ) : ( + none found in this project ); - } else { - rows["Apps"] = none found in this project; - rows["Stacks"] = ( + + rows["Stacks"] = + overview.stacks.length > 0 ? ( {overview.stacks.length} total {overview.stacks.slice(0, 5).map((stack) => ( - {stack.id}: {stack.services} services, {stack.volumes} volumes + {stack.shortId ?? stack.id}: {stack.services} services,{" "} + {stack.volumes} volumes {stack.description ? ` (${stack.description})` : ""} ))} + ) : ( + none found in this project ); - rows["Containers"] = ( + + rows["Containers"] = + overview.containers.length > 0 ? ( {overview.containers.length} total {overview.containers.slice(0, 8).map((container) => ( - {container.name}: {container.status} - {container.stackId ? ` (stack ${container.stackId})` : ""} + {container.shortId + ? `${container.shortId} (${container.name})` + : container.name} + : {container.status} + {container.stackId + ? ` (stack ${stackDisplayById.get(container.stackId) ?? container.stackId})` + : ""} ))} + ) : ( + none found in this project ); - } return ; }; @@ -241,198 +219,21 @@ const GetContext: FC<{ ctx: Context }> = ({ ctx }) => { const appInstallationId = values["installation-id"]?.value; const resolvedProject = usePromise( - async ( + ( contextProjectId: string | undefined, installationId: string | undefined, - ): Promise<{ - projectId?: string; - resolvedFrom?: "project-id" | "installation-id"; - unavailableReason?: string; - }> => { - if (contextProjectId) { - return { projectId: contextProjectId, resolvedFrom: "project-id" }; - } - - if (!installationId) { - return { - unavailableReason: - "no project-id in context and no installation-id to derive it from", - }; - } - - try { - const installation = await getAppInstallationFromUuid( - apiClient, - installationId, - ); - return { - projectId: installation.projectId, - resolvedFrom: "installation-id", - }; - } catch { - return { - unavailableReason: "could not resolve project from installation-id", - }; - } - }, + ) => resolveProjectContext(apiClient, contextProjectId, installationId), [projectIdFromContext, appInstallationId], ); const overview = usePromise( - async ( - projectId: string | undefined, - resolvedFrom: "project-id" | "installation-id" | undefined, - unavailableReason: string | undefined, - ): Promise => { - if (!projectId) { - return { - apps: [], - stacks: [], - containers: [], - unavailableReason: - unavailableReason ?? "project could not be resolved", - }; - } - - try { - const projectResponse = await apiClient.project.getProject({ - projectId, - }); - assertStatus(projectResponse, 200); - - const appInstallationsResponse = - await apiClient.app.listAppinstallations({ projectId }); - assertStatus(appInstallationsResponse, 200); - - const appInstallations = appInstallationsResponse.data; - const uniqueAppIds = Array.from( - new Set(appInstallations.map((installation) => installation.appId)), - ); - - const appNames = new Map(); - await Promise.all( - uniqueAppIds.map(async (appId) => { - try { - const app = await getAppFromUuid(apiClient, appId); - appNames.set(appId, app.name); - } catch { - appNames.set(appId, appId); - } - }), - ); - - const databaseById = new Map< - string, - { name: string; kind: "mysql" | "redis" } - >(); - - try { - const mysqlResponse = await apiClient.database.listMysqlDatabases({ - projectId, - }); - assertStatus(mysqlResponse, 200); - for (const db of mysqlResponse.data) { - databaseById.set(db.id, { name: db.name, kind: "mysql" }); - } - } catch { - // best effort - } - - try { - const redisResponse = await apiClient.database.listRedisDatabases({ - projectId, - }); - assertStatus(redisResponse, 200); - for (const db of redisResponse.data) { - databaseById.set(db.id, { name: db.name, kind: "redis" }); - } - } catch { - // best effort - } - - const apps: AppSummary[] = appInstallations.map((installation) => { - const linkedDatabases: LinkedDatabaseSummary[] = - installation.linkedDatabases.map((linked: AppLinkedDatabase) => { - const resolved = databaseById.get(linked.databaseId); - return { - databaseId: linked.databaseId, - purpose: linked.purpose, - kind: resolved?.kind ?? "unknown", - name: resolved?.name, - }; - }); - - return { - installationId: installation.id, - appId: installation.appId, - appName: appNames.get(installation.appId) ?? installation.appId, - installationPath: installation.installationPath, - linkedDatabases, - }; - }); - - if (apps.length > 0) { - return { - projectId, - projectName: projectResponse.data.description, - resolvedFrom, - apps, - stacks: [], - containers: [], - }; - } - - const stackResponse = await apiClient.container.listStacks({ - projectId, - }); - assertStatus(stackResponse, 200); - - const serviceResponse = await apiClient.container.listServices({ - projectId, - }); - assertStatus(serviceResponse, 200); - - const stacks: StackSummary[] = stackResponse.data.map((stack) => ({ - id: stack.id, - description: stack.description, - services: stack.services?.length ?? 0, - volumes: stack.volumes?.length ?? 0, - })); - - const containers: ContainerSummary[] = serviceResponse.data.map( - (service) => ({ - id: service.id, - name: service.serviceName, - status: service.status, - stackId: service.stackId, - }), - ); - - return { - projectId, - projectName: projectResponse.data.description, - resolvedFrom, - apps, - stacks, - containers, - }; - } catch { - return { - projectId, - resolvedFrom, - apps: [], - stacks: [], - containers: [], - unavailableReason: - "project-level data could not be fetched with current access/context", - }; - } - }, - [ - resolvedProject.projectId, - resolvedProject.resolvedFrom, - resolvedProject.unavailableReason, - ], + (resolvedProjectContext: { + projectId?: string; + resolvedFrom?: "project-id" | "installation-id"; + unavailableReason?: string; + }): Promise => + fetchProjectOverview(apiClient, resolvedProjectContext), + [resolvedProject], ); if (renderAsJson) { diff --git a/src/lib/context/projectOverview.ts b/src/lib/context/projectOverview.ts new file mode 100644 index 000000000..6b7fb2361 --- /dev/null +++ b/src/lib/context/projectOverview.ts @@ -0,0 +1,235 @@ +import { MittwaldAPIV2, MittwaldAPIV2Client } from "@mittwald/api-client"; +import { assertStatus } from "@mittwald/api-client-commons"; +import { + getAppFromUuid, + getAppInstallationFromUuid, +} from "../resources/app/uuid.js"; + +type AppLinkedDatabase = MittwaldAPIV2.Components.Schemas.AppLinkedDatabase; + +export type LinkedDatabaseSummary = { + databaseId: string; + purpose: string; + kind: "mysql" | "redis" | "unknown"; + name?: string; +}; + +export type AppSummary = { + installationId: string; + installationShortId?: string; + appId: string; + appName: string; + installationPath: string; + linkedDatabases: LinkedDatabaseSummary[]; +}; + +export type StackSummary = { + id: string; + shortId?: string; + description?: string; + services: number; + volumes: number; +}; + +export type ContainerSummary = { + id: string; + shortId?: string; + name: string; + status: string; + stackId?: string; +}; + +export type ResolvedProjectContext = { + projectId?: string; + resolvedFrom?: "project-id" | "installation-id"; + unavailableReason?: string; +}; + +export type ProjectOverview = { + projectId?: string; + projectShortId?: string; + projectName?: string; + resolvedFrom?: "project-id" | "installation-id"; + apps: AppSummary[]; + stacks: StackSummary[]; + containers: ContainerSummary[]; + unavailableReason?: string; +}; + +export async function resolveProjectContext( + apiClient: MittwaldAPIV2Client, + contextProjectId: string | undefined, + installationId: string | undefined, +): Promise { + if (contextProjectId) { + return { projectId: contextProjectId, resolvedFrom: "project-id" }; + } + + if (!installationId) { + return { + unavailableReason: + "no project-id in context and no installation-id to derive it from", + }; + } + + try { + const installation = await getAppInstallationFromUuid( + apiClient, + installationId, + ); + return { + projectId: installation.projectId, + resolvedFrom: "installation-id", + }; + } catch { + return { + unavailableReason: "could not resolve project from installation-id", + }; + } +} + +export async function fetchProjectOverview( + apiClient: MittwaldAPIV2Client, + resolvedProject: ResolvedProjectContext, +): Promise { + const { projectId, resolvedFrom, unavailableReason } = resolvedProject; + + if (!projectId) { + return { + apps: [], + stacks: [], + containers: [], + unavailableReason: unavailableReason ?? "project could not be resolved", + }; + } + + try { + const projectResponse = await apiClient.project.getProject({ + projectId, + }); + assertStatus(projectResponse, 200); + + const appInstallationsResponse = await apiClient.app.listAppinstallations({ + projectId, + }); + assertStatus(appInstallationsResponse, 200); + + const appInstallations = appInstallationsResponse.data; + const uniqueAppIds = Array.from( + new Set(appInstallations.map((installation) => installation.appId)), + ); + + const appNames = new Map(); + await Promise.all( + uniqueAppIds.map(async (appId) => { + try { + const app = await getAppFromUuid(apiClient, appId); + appNames.set(appId, app.name); + } catch { + appNames.set(appId, appId); + } + }), + ); + + const databaseById = new Map< + string, + { name: string; kind: "mysql" | "redis" } + >(); + + try { + const mysqlResponse = await apiClient.database.listMysqlDatabases({ + projectId, + }); + assertStatus(mysqlResponse, 200); + for (const db of mysqlResponse.data) { + databaseById.set(db.id, { name: db.name, kind: "mysql" }); + } + } catch { + // best effort + } + + try { + const redisResponse = await apiClient.database.listRedisDatabases({ + projectId, + }); + assertStatus(redisResponse, 200); + for (const db of redisResponse.data) { + databaseById.set(db.id, { name: db.name, kind: "redis" }); + } + } catch { + // best effort + } + + const apps: AppSummary[] = appInstallations.map((installation) => { + const linkedDatabases: LinkedDatabaseSummary[] = + installation.linkedDatabases.map((linked: AppLinkedDatabase) => { + const resolved = databaseById.get(linked.databaseId); + return { + databaseId: linked.databaseId, + purpose: linked.purpose, + kind: resolved?.kind ?? "unknown", + name: resolved?.name, + }; + }); + + return { + installationId: installation.id, + installationShortId: installation.shortId, + appId: installation.appId, + appName: appNames.get(installation.appId) ?? installation.appId, + installationPath: installation.installationPath, + linkedDatabases, + }; + }); + + let stacks: StackSummary[] = []; + let containers: ContainerSummary[] = []; + + try { + const [stackResponse, serviceResponse] = await Promise.all([ + apiClient.container.listStacks({ projectId }), + apiClient.container.listServices({ projectId }), + ]); + assertStatus(stackResponse, 200); + assertStatus(serviceResponse, 200); + + stacks = stackResponse.data.map((stack) => ({ + id: stack.id, + shortId: (stack as { shortId?: string }).shortId, + description: stack.description, + services: stack.services?.length ?? 0, + volumes: stack.volumes?.length ?? 0, + })); + + containers = serviceResponse.data.map((service) => ({ + id: service.id, + shortId: (service as { shortId?: string }).shortId, + name: service.serviceName, + status: service.status, + stackId: service.stackId, + })); + } catch { + // best effort + } + + return { + projectId, + projectShortId: (projectResponse.data as { shortId?: string }).shortId, + projectName: projectResponse.data.description, + resolvedFrom, + apps, + stacks, + containers, + }; + } catch { + return { + projectId, + resolvedFrom, + apps: [], + stacks: [], + containers: [], + unavailableReason: + "project-level data could not be fetched with current access/context", + }; + } +} From d80e5906a3a04f2600f2886c6e78c32d18007467 Mon Sep 17 00:00:00 2001 From: Lars Bergmann Date: Mon, 27 Jul 2026 09:34:21 +0200 Subject: [PATCH 3/5] format and highlighting for project overview entries --- src/commands/context/get.tsx | 53 ++++++++++++++++++------------ src/lib/context/projectOverview.ts | 16 +++++++++ 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/commands/context/get.tsx b/src/commands/context/get.tsx index 72026890d..79d5e6a17 100644 --- a/src/commands/context/get.tsx +++ b/src/commands/context/get.tsx @@ -16,6 +16,7 @@ import Context, { } from "../../lib/context/Context.js"; import { fetchProjectOverview, + formatOverviewEntry, ProjectOverview, resolveProjectContext, } from "../../lib/context/projectOverview.js"; @@ -84,7 +85,7 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ overview, }) => { const stackDisplayById = new Map( - overview.stacks.map((stack) => [stack.id, stack.shortId ?? stack.id]), + overview.stacks.map((stack) => [stack.id, stack.shortId ?? ""]), ); if (overview.unavailableReason) { @@ -112,12 +113,13 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ {overview.apps.map((app) => ( - - {app.appName}{" "} - - ({app.installationPath},{" "} - {app.installationShortId ?? app.installationId}) - + + {formatOverviewEntry({ + shortId: app.installationShortId, + name: app.appName, + status: `installed at ${app.installationPath}`, + id: app.installationId, + })} {app.linkedDatabases.length > 0 ? ( app.linkedDatabases.map((db) => ( @@ -146,9 +148,12 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ {overview.stacks.slice(0, 5).map((stack) => ( - {stack.shortId ?? stack.id}: {stack.services} services,{" "} - {stack.volumes} volumes - {stack.description ? ` (${stack.description})` : ""} + {formatOverviewEntry({ + shortId: stack.shortId, + name: stack.description ?? "stack", + status: `${stack.services} services, ${stack.volumes} volumes`, + id: stack.id, + })} ))} @@ -162,17 +167,23 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ {overview.containers.length} total - {overview.containers.slice(0, 8).map((container) => ( - - {container.shortId - ? `${container.shortId} (${container.name})` - : container.name} - : {container.status} - {container.stackId - ? ` (stack ${stackDisplayById.get(container.stackId) ?? container.stackId})` - : ""} - - ))} + {overview.containers.slice(0, 8).map((container) => { + const stackShortId = container.stackId + ? stackDisplayById.get(container.stackId) ?? "" + : ""; + const stackSuffix = container.stackId ? ` | stack ${stackShortId}` : ""; + + return ( + + {formatOverviewEntry({ + shortId: container.shortId, + name: container.name, + status: `${container.status}${stackSuffix}`, + id: container.id, + })} + + ); + })} ) : ( none found in this project diff --git a/src/lib/context/projectOverview.ts b/src/lib/context/projectOverview.ts index 6b7fb2361..cdbfa4be8 100644 --- a/src/lib/context/projectOverview.ts +++ b/src/lib/context/projectOverview.ts @@ -56,6 +56,22 @@ export type ProjectOverview = { unavailableReason?: string; }; +export type OverviewEntryData = { + shortId?: string; + name: string; + status: string; + id: string; +}; + +export function formatOverviewEntry({ + shortId, + name, + status, + id, +}: OverviewEntryData): string { + return `${shortId ?? ""} ( ${name} ): ${status} ( ${id} )`; +} + export async function resolveProjectContext( apiClient: MittwaldAPIV2Client, contextProjectId: string | undefined, From f83d21f80a97f0f11fb34c91b11ec0dc56ba55cf Mon Sep 17 00:00:00 2001 From: Lars Bergmann Date: Mon, 27 Jul 2026 09:35:18 +0200 Subject: [PATCH 4/5] Make linter happy --- src/commands/context/get.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/commands/context/get.tsx b/src/commands/context/get.tsx index 79d5e6a17..14e1bbc9b 100644 --- a/src/commands/context/get.tsx +++ b/src/commands/context/get.tsx @@ -169,9 +169,11 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ {overview.containers.slice(0, 8).map((container) => { const stackShortId = container.stackId - ? stackDisplayById.get(container.stackId) ?? "" + ? (stackDisplayById.get(container.stackId) ?? "") + : ""; + const stackSuffix = container.stackId + ? ` | stack ${stackShortId}` : ""; - const stackSuffix = container.stackId ? ` | stack ${stackShortId}` : ""; return ( From 9382769116045b06120a0ee5b45309b1eeb99c44 Mon Sep 17 00:00:00 2001 From: Lars Bergmann Date: Mon, 27 Jul 2026 09:41:22 +0200 Subject: [PATCH 5/5] Improve output, move resolved from into project line --- src/commands/context/get.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/context/get.tsx b/src/commands/context/get.tsx index 14e1bbc9b..62b5133e1 100644 --- a/src/commands/context/get.tsx +++ b/src/commands/context/get.tsx @@ -101,11 +101,11 @@ const ProjectOverviewSection: FC<{ overview: ProjectOverview }> = ({ {overview.projectName ?? overview.projectId}{" "} - ({overview.projectShortId ?? overview.projectId}) + ({overview.projectShortId ?? overview.projectId}, resolved from{" "} + {overview.resolvedFrom ?? "project-id"}) ), - "Resolved from": {overview.resolvedFrom ?? "project-id"}, }; rows["Apps"] =