fix(docker): remove word break in details [EE-4481] (#7996)

pull/8101/head
Chaim Lev-Ari 2022-11-22 15:00:55 +02:00 committed by GitHub
parent fe8e834dbf
commit d484a0eb64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 178 additions and 98 deletions

View File

@ -1,5 +1,5 @@
import _ from 'lodash-es';
import { trimSHA } from './utils';
import { joinCommand, trimSHA } from './utils';
function includeString(text, values) {
return values.some(function (val) {
@ -191,12 +191,7 @@ angular
};
})
.filter('command', function () {
'use strict';
return function (command) {
if (command) {
return command.join(' ');
}
};
return joinCommand;
})
.filter('hideshasum', function () {
'use strict';

View File

@ -9,3 +9,11 @@ export function trimSHA(imageName: string) {
}
return _.split(imageName, '@sha256')[0];
}
export function joinCommand(command: null | Array<string> = []) {
if (!command) {
return '';
}
return command.join(' ');
}

View File

@ -9,9 +9,13 @@ import { Gpu } from '@/react/docker/containers/CreateView/Gpu';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter';
import { DockerfileDetails } from '@/react/docker/images/ItemView/DockerfileDetails';
import { HealthStatus } from '@/react/docker/containers/ItemView/HealthStatus';
export const componentsModule = angular
.module('portainer.docker.react.components', [])
.component('dockerfileDetails', r2a(DockerfileDetails, ['image']))
.component('dockerHealthStatus', r2a(HealthStatus, ['health']))
.component(
'containerQuickActions',
r2a(withUIRouter(withCurrentUser(ContainerQuickActions)), [

View File

@ -162,40 +162,7 @@
</access-control-panel>
<!-- !access-control-panel -->
<div ng-if="container.State.Health" class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title-text="Container health"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Status</td>
<td>
<i
ng-class="
{ healthy: 'fa fa-heartbeat space-right green-icon', unhealthy: 'fa fa-heartbeat space-right red-icon', starting: 'fa fa-heartbeat space-right orange-icon' }[
container.State.Health.Status
]
"
></i>
{{ container.State.Health.Status }}
</td>
</tr>
<tr>
<td>Failure count</td>
<td>{{ container.State.Health.FailingStreak }}</td>
</tr>
<tr>
<td>Last output</td>
<td>{{ container.State.Health.Log[container.State.Health.Log.length - 1].Output }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<docker-health-status ng-if="container.State.Health" health="container.State.Health"></docker-health-status>
<div class="row" authorization="DockerImageCreate">
<div class="col-lg-12 col-md-12 col-xs-12">

View File

@ -162,58 +162,8 @@
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="list" feather-icon="true" title-text="Dockerfile details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>CMD</td>
<td
><code>{{ image.Command | command }}</code></td
>
</tr>
<tr ng-if="image.Entrypoint">
<td>ENTRYPOINT</td>
<td
><code>{{ image.Entrypoint | command }}</code></td
>
</tr>
<tr ng-if="image.ExposedPorts.length > 0">
<td>EXPOSE</td>
<td>
<span class="label label-default space-right" ng-repeat="port in image.ExposedPorts">
{{ port }}
</span>
</td>
</tr>
<tr ng-if="image.Volumes.length > 0">
<td>VOLUME</td>
<td>
<span class="label label-default space-right" ng-repeat="volume in image.Volumes">
{{ volume }}
</span>
</td>
</tr>
<tr ng-if="image.Env.length > 0">
<td>ENV</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="var in image.Env">
<td>{{ var|key: '=' }}</td>
<td>{{ var|value: '=' }}</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<dockerfile-details ng-if="image" image="image"></dockerfile-details>
<div class="row" ng-if="history.length > 0">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>

View File

@ -1,15 +1,29 @@
import clsx from 'clsx';
import { ReactNode } from 'react';
interface Props {
children?: ReactNode;
label: string;
colClassName?: string;
className?: string;
}
export function DetailsRow({ label, children }: Props) {
export function DetailsRow({
label,
children,
colClassName,
className,
}: Props) {
return (
<tr>
<td>{label}</td>
{children && <td data-cy={`detailsTable-${label}Value`}>{children}</td>}
<tr className={className}>
<td className={clsx(colClassName, '!break-normal min-w-[150px]')}>
{label}
</td>
{children && (
<td className={colClassName} data-cy={`detailsTable-${label}Value`}>
{children}
</td>
)}
</tr>
);
}

View File

@ -0,0 +1,54 @@
import { ComponentProps } from 'react';
import { Server } from 'react-feather';
import { TableContainer, TableTitle } from '@@/datatables';
import { DetailsTable } from '@@/DetailsTable';
import { Icon } from '@@/Icon';
import { Health } from '../types/response';
const StatusMode: Record<
Health['Status'],
ComponentProps<typeof Icon>['mode']
> = {
healthy: 'success',
unhealthy: 'danger',
starting: 'warning',
};
interface Props {
health: Health;
}
export function HealthStatus({ health }: Props) {
return (
<div className="row">
<div className="col-lg-12 col-md-12 col-xs-12">
<TableContainer>
<TableTitle label="Container health" icon={Server} />
<DetailsTable>
<DetailsTable.Row label="Status">
<div className="vertical-center">
<Icon
icon="fa fa-heartbeat"
mode={StatusMode[health.Status]}
className="space-right"
/>
{health.Status}
</div>
</DetailsTable.Row>
<DetailsTable.Row label="Failure count">
{health.FailingStreak}
</DetailsTable.Row>
<DetailsTable.Row label="Last output">
{health.Log[health.Log.length - 1].Output}
</DetailsTable.Row>
</DetailsTable>
</TableContainer>
</div>
</div>
);
}

View File

@ -70,6 +70,12 @@ interface MountPoint {
Propagation: MountPropagation;
}
export interface Health {
Status: 'healthy' | 'unhealthy' | 'starting';
FailingStreak: number;
Log: Array<{ Output: string }>;
}
export interface DockerContainerResponse {
Id: string;
Names: string[];
@ -88,7 +94,6 @@ export interface DockerContainerResponse {
};
NetworkSettings?: SummaryNetworkSettings;
Mounts: MountPoint[];
Portainer: PortainerMetadata;
IsPortainer: boolean;
}

View File

@ -0,0 +1,83 @@
import { List } from 'react-feather';
import { joinCommand } from '@/docker/filters/utils';
import { getPairKey, getPairValue } from '@/portainer/filters/filters';
import { TableContainer, TableTitle } from '@@/datatables';
import { DetailsTable } from '@@/DetailsTable';
interface DockerImage {
Command: Array<string>;
Entrypoint: Array<string>;
ExposedPorts: Array<number>;
Volumes: Array<string>;
Env: Array<string>;
}
interface Props {
image: DockerImage;
}
export function DockerfileDetails({ image }: Props) {
return (
<div className="row">
<div className="col-lg-12 col-md-12 col-xs-12">
<TableContainer>
<TableTitle label="Dockerfile details" icon={List} />
<DetailsTable>
<DetailsTable.Row label="CMD">
<code>{joinCommand(image.Command)}</code>
</DetailsTable.Row>
{image.Entrypoint && (
<DetailsTable.Row label="ENTRYPOINT">
<code>{joinCommand(image.Entrypoint)}</code>
</DetailsTable.Row>
)}
{image.ExposedPorts.length > 0 && (
<DetailsTable.Row label="EXPOSE">
{image.ExposedPorts.map((port, index) => (
<span className="label label-default space-right" key={index}>
{port}
</span>
))}
</DetailsTable.Row>
)}
{image.Volumes.length > 0 && (
<DetailsTable.Row label="VOLUME">
<div className="flex flex-wrap gap-1">
{image.Volumes.map((volume, index) => (
<span
key={index}
className="label label-default space-right"
ng-repeat="volume in image.Volumes"
>
{volume}
</span>
))}
</div>
</DetailsTable.Row>
)}
{image.Env.length > 0 && (
<DetailsTable.Row label="ENV">
<table className="table table-bordered table-condensed">
<tbody>
{image.Env.map((variable) => (
<tr key={variable}>
<td>{getPairKey(variable, '=')}</td>
<td>{getPairValue(variable, '=')}</td>
</tr>
))}
</tbody>
</table>
</DetailsTable.Row>
)}
</DetailsTable>
</TableContainer>
</div>
</div>
);
}