mirror of https://github.com/portainer/portainer
fix(docker): remove word break in details [EE-4481] (#7996)
parent
fe8e834dbf
commit
d484a0eb64
|
@ -1,5 +1,5 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { trimSHA } from './utils';
|
import { joinCommand, trimSHA } from './utils';
|
||||||
|
|
||||||
function includeString(text, values) {
|
function includeString(text, values) {
|
||||||
return values.some(function (val) {
|
return values.some(function (val) {
|
||||||
|
@ -191,12 +191,7 @@ angular
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter('command', function () {
|
.filter('command', function () {
|
||||||
'use strict';
|
return joinCommand;
|
||||||
return function (command) {
|
|
||||||
if (command) {
|
|
||||||
return command.join(' ');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
.filter('hideshasum', function () {
|
.filter('hideshasum', function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
|
@ -9,3 +9,11 @@ export function trimSHA(imageName: string) {
|
||||||
}
|
}
|
||||||
return _.split(imageName, '@sha256')[0];
|
return _.split(imageName, '@sha256')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function joinCommand(command: null | Array<string> = []) {
|
||||||
|
if (!command) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return command.join(' ');
|
||||||
|
}
|
||||||
|
|
|
@ -9,9 +9,13 @@ import { Gpu } from '@/react/docker/containers/CreateView/Gpu';
|
||||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
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
|
export const componentsModule = angular
|
||||||
.module('portainer.docker.react.components', [])
|
.module('portainer.docker.react.components', [])
|
||||||
|
.component('dockerfileDetails', r2a(DockerfileDetails, ['image']))
|
||||||
|
.component('dockerHealthStatus', r2a(HealthStatus, ['health']))
|
||||||
.component(
|
.component(
|
||||||
'containerQuickActions',
|
'containerQuickActions',
|
||||||
r2a(withUIRouter(withCurrentUser(ContainerQuickActions)), [
|
r2a(withUIRouter(withCurrentUser(ContainerQuickActions)), [
|
||||||
|
|
|
@ -162,40 +162,7 @@
|
||||||
</access-control-panel>
|
</access-control-panel>
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
||||||
<div ng-if="container.State.Health" class="row">
|
<docker-health-status ng-if="container.State.Health" health="container.State.Health"></docker-health-status>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="row" authorization="DockerImageCreate">
|
<div class="row" authorization="DockerImageCreate">
|
||||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
|
|
|
@ -162,58 +162,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<dockerfile-details ng-if="image" image="image"></dockerfile-details>
|
||||||
<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>
|
|
||||||
<div class="row" ng-if="history.length > 0">
|
<div class="row" ng-if="history.length > 0">
|
||||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
label: string;
|
label: string;
|
||||||
|
colClassName?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DetailsRow({ label, children }: Props) {
|
export function DetailsRow({
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
colClassName,
|
||||||
|
className,
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr className={className}>
|
||||||
<td>{label}</td>
|
<td className={clsx(colClassName, '!break-normal min-w-[150px]')}>
|
||||||
{children && <td data-cy={`detailsTable-${label}Value`}>{children}</td>}
|
{label}
|
||||||
|
</td>
|
||||||
|
{children && (
|
||||||
|
<td className={colClassName} data-cy={`detailsTable-${label}Value`}>
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -70,6 +70,12 @@ interface MountPoint {
|
||||||
Propagation: MountPropagation;
|
Propagation: MountPropagation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Health {
|
||||||
|
Status: 'healthy' | 'unhealthy' | 'starting';
|
||||||
|
FailingStreak: number;
|
||||||
|
Log: Array<{ Output: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DockerContainerResponse {
|
export interface DockerContainerResponse {
|
||||||
Id: string;
|
Id: string;
|
||||||
Names: string[];
|
Names: string[];
|
||||||
|
@ -88,7 +94,6 @@ export interface DockerContainerResponse {
|
||||||
};
|
};
|
||||||
NetworkSettings?: SummaryNetworkSettings;
|
NetworkSettings?: SummaryNetworkSettings;
|
||||||
Mounts: MountPoint[];
|
Mounts: MountPoint[];
|
||||||
|
|
||||||
Portainer: PortainerMetadata;
|
Portainer: PortainerMetadata;
|
||||||
IsPortainer: boolean;
|
IsPortainer: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue