refactor(docker/containers): migrate inspect view to react [EE-2190] (#11005)

pull/11554/head
Chaim Lev-Ari 8 months ago committed by GitHub
parent de473fc10e
commit 2100155ab5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,7 +2,6 @@ import 'bootstrap/dist/css/bootstrap.css';
import 'toastr/build/toastr.css';
import 'xterm/dist/xterm.css';
import 'angularjs-slider/dist/rzslider.css';
import 'angular-json-tree/dist/angular-json-tree.css';
import 'angular-loading-bar/build/loading-bar.css';
import 'angular-moment-picker/dist/angular-moment-picker.min.css';
import 'spinkit/spinkit.min.css';

@ -1,5 +1,5 @@
import _ from 'lodash-es';
import { hideShaSum, joinCommand, nodeStatusBadge, taskStatusBadge, trimSHA, trimVersionTag } from './utils';
import { hideShaSum, joinCommand, nodeStatusBadge, taskStatusBadge, trimContainerName, trimSHA, trimVersionTag } from './utils';
function includeString(text, values) {
return values.some(function (val) {
@ -76,15 +76,7 @@ angular
};
})
.filter('nodestatusbadge', () => nodeStatusBadge)
.filter('trimcontainername', function () {
'use strict';
return function (name) {
if (name) {
return name.indexOf('/') === 0 ? name.slice(1) : name;
}
return '';
};
})
.filter('trimcontainername', () => trimContainerName)
.filter('getstatetext', function () {
'use strict';
return function (state) {

@ -83,3 +83,10 @@ export function nodeStatusBadge(text: NodeStatus['State']) {
export function hideShaSum(imageName = '') {
return imageName.split('@sha')[0];
}
export function trimContainerName(name?: string) {
if (name) {
return name.indexOf('/') === 0 ? name.slice(1) : name;
}
return '';
}

@ -8,6 +8,7 @@ import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter';
import { LogView } from '@/react/docker/containers/LogView';
import { CreateView } from '@/react/docker/containers/CreateView';
import { InspectView } from '@/react/docker/containers/InspectView/InspectView';
export const containersModule = angular
.module('portainer.docker.react.views.containers', [])
@ -26,7 +27,10 @@ export const containersModule = angular
'containerLogView',
r2a(withUIRouter(withReactQuery(withCurrentUser(LogView))), [])
)
.component(
'dockerContainerInspectView',
r2a(withUIRouter(withReactQuery(withCurrentUser(InspectView))), [])
)
.config(config).name;
/* @ngInject */
@ -95,8 +99,7 @@ function config($stateRegistryProvider: StateRegistry) {
url: '/inspect',
views: {
'content@': {
templateUrl: '~@/docker/views/containers/inspect/containerinspect.html',
controller: 'ContainerInspectController',
component: 'dockerContainerInspectView',
},
},
});

@ -1,27 +0,0 @@
angular.module('portainer.docker').controller('ContainerInspectController', [
'$scope',
'$transition$',
'Notifications',
'ContainerService',
'HttpRequestHelper',
'endpoint',
function ($scope, $transition$, Notifications, ContainerService, HttpRequestHelper, endpoint) {
$scope.state = {
DisplayTextView: false,
};
$scope.containerInfo = {};
function initView() {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
ContainerService.inspect(endpoint.Id, $transition$.params().id)
.then(function success(d) {
$scope.containerInfo = d;
})
.catch(function error(e) {
Notifications.error('Failure', e, 'Unable to inspect container');
});
}
initView();
},
]);

@ -1,28 +0,0 @@
<page-header
title="'Container inspect'"
breadcrumbs="[
{ label:'Containers', link:'docker.containers' },
{
label:(containerInfo.Name | trimcontainername),
link: 'docker.containers.container',
linkParams: { id: containerInfo.Id },
}, 'Inspect']"
>
</page-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="circle" title-text="Inspect">
<span class="btn-group btn-group-sm">
<label class="btn btn-light" ng-model="state.DisplayTextView" uib-btn-radio="false"><pr-icon icon="'code'"></pr-icon>Tree</label>
<label class="btn btn-light" ng-model="state.DisplayTextView" uib-btn-radio="true"><pr-icon icon="'file'"></pr-icon>Text</label>
</span>
</rd-widget-header>
<rd-widget-body>
<pre ng-show="state.DisplayTextView">{{ containerInfo | json: 4 }}</pre>
<json-tree ng-hide="state.DisplayTextView" object="containerInfo" root-name="containerInfo.Id" start-expanded="true"></json-tree>
</rd-widget-body>
</rd-widget>
</div>
</div>

@ -34,7 +34,6 @@ angular
'ngResource',
'angularUtils.directives.dirPagination',
'LocalStorageModule',
'angular-json-tree',
'angular-loading-bar',
'angular-clipboard',
'ngFileSaver',

@ -1,30 +1,25 @@
/* json-tree override */
.json-tree,
json-tree {
.json-tree {
font-size: 13px;
color: var(--blue-5);
}
.json-tree .key,
json-tree .key {
.json-tree .key {
color: var(--text-json-tree-color);
padding-right: 4px;
}
json-tree .key {
padding-right: 5px;
.json-tree .chevronIcon {
color: var(--text-json-tree-color);
}
.json-tree .branch-preview,
json-tree .branch-preview {
.json-tree .branch-preview {
color: var(--text-json-tree-branch-preview-color);
font-style: normal;
font-size: 11px;
opacity: 0.5;
}
.json-tree .leaf-value,
json-tree .leaf-value {
.json-tree .leaf-value {
color: var(--text-json-tree-leaf-color);
}
/* !json-tree override */

@ -3,7 +3,7 @@ import { JsonView, defaultStyles } from 'react-json-view-lite';
import 'react-json-view-lite/dist/index.css';
import clsx from 'clsx';
import './JsonTree.css';
import styles from './JsonTree.module.css';
export function JsonTree({ style, ...props }: ComponentProps<typeof JsonView>) {
const currentStyle = getCurrentStyle(style);
@ -25,17 +25,20 @@ function getCurrentStyle(style: StyleProps | undefined): StyleProps {
return {
...defaultStyles,
container: 'json-tree',
booleanValue: 'leaf-value',
nullValue: 'leaf-value',
otherValue: 'leaf-value',
numberValue: 'leaf-value',
stringValue: 'leaf-value',
undefinedValue: 'leaf-value',
label: 'key',
punctuation: 'leaf-value',
collapseIcon: clsx(defaultStyles.collapseIcon, 'key'),
expandIcon: clsx(defaultStyles.expandIcon, 'key'),
collapsedContent: clsx(defaultStyles.collapsedContent, 'branch-preview'),
container: styles.jsonTree,
booleanValue: styles.leafValue,
nullValue: styles.leafValue,
otherValue: styles.leafValue,
numberValue: styles.leafValue,
stringValue: styles.leafValue,
undefinedValue: styles.leafValue,
label: styles.key,
punctuation: styles.leafValue,
collapseIcon: clsx(defaultStyles.collapseIcon, styles.chevronIcon),
expandIcon: clsx(defaultStyles.expandIcon, styles.chevronIcon),
collapsedContent: clsx(
defaultStyles.collapsedContent,
styles.branchPreview
),
};
}

@ -1,5 +1,5 @@
import clsx from 'clsx';
import { PropsWithChildren, ReactNode } from 'react';
import { ComponentProps, PropsWithChildren, ReactNode } from 'react';
import { AutomationTestingProps } from '@/types';
@ -12,6 +12,7 @@ export interface Option<T> {
value: T;
label?: ReactNode;
disabled?: boolean;
icon?: ComponentProps<typeof Button>['icon'];
}
interface Props<T> {
@ -49,6 +50,7 @@ export function ButtonSelector<T extends string | number | boolean>({
onChange={() => onChange(option.value)}
disabled={disabled || option.disabled}
readOnly={readOnly}
icon={option.icon}
>
{option.label || option.value.toString()}
</OptionItem>
@ -62,6 +64,7 @@ interface OptionItemProps {
onChange(): void;
disabled?: boolean;
readOnly?: boolean;
icon?: ComponentProps<typeof Button>['icon'];
}
function OptionItem({
@ -71,6 +74,7 @@ function OptionItem({
disabled,
readOnly,
'data-cy': dataCy,
icon,
}: PropsWithChildren<OptionItemProps> & AutomationTestingProps) {
return (
<Button
@ -84,6 +88,7 @@ function OptionItem({
'!static !z-auto'
)}
data-cy={dataCy}
icon={icon}
>
{children}
<input

@ -0,0 +1,79 @@
import { useCurrentStateAndParams } from '@uirouter/react';
import { Circle, Code as CodeIcon, File } from 'lucide-react';
import { useState } from 'react';
import { trimContainerName } from '@/docker/filters/utils';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { JsonTree } from '@@/JsonTree';
import { PageHeader } from '@@/PageHeader';
import { Widget } from '@@/Widget';
import { ButtonSelector } from '@@/form-components/ButtonSelector/ButtonSelector';
import { Code } from '@@/Code';
import { useContainerInspect } from '../queries/useContainerInspect';
export function InspectView() {
const environmentId = useEnvironmentId();
const {
params: { id, nodeName },
} = useCurrentStateAndParams();
const inspectQuery = useContainerInspect(environmentId, id, { nodeName });
const [viewType, setViewType] = useState<'tree' | 'text'>('tree');
if (!inspectQuery.data) {
return null;
}
const containerInfo = inspectQuery.data;
return (
<>
<PageHeader
title="Container inspect"
breadcrumbs={[
{ label: 'Containers', link: 'docker.containers' },
{
label: trimContainerName(containerInfo.Name),
link: '^',
// linkParams: { id: containerInfo.Id },
},
'Inspect',
]}
/>
<div className="row">
<div className="col-lg-12 col-md-12 col-xs-12">
<Widget>
<Widget.Title icon={Circle} title="Inspect">
<ButtonSelector<'tree' | 'text'>
onChange={(value) => setViewType(value)}
value={viewType}
options={[
{
label: 'Tree',
icon: CodeIcon,
value: 'tree',
},
{
label: 'Text',
icon: File,
value: 'text',
},
]}
/>
</Widget.Title>
<Widget.Body>
{viewType === 'text' && (
<Code showCopyButton>
{JSON.stringify(containerInfo, undefined, 4)}
</Code>
)}
{viewType === 'tree' && <JsonTree data={containerInfo} />}
</Widget.Body>
</Widget>
</div>
</div>
</>
);
}

@ -0,0 +1,39 @@
import { useQuery } from 'react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { genericHandler } from '@/docker/rest/response/handlers';
import { ContainerId } from '../types';
import { urlBuilder } from '../containers.service';
import { addNodeName } from '../../proxy/addNodeName';
import { queryKeys } from './query-keys';
import { ContainerJSON } from './container';
export function useContainerInspect(
environmentId: EnvironmentId,
id: ContainerId,
params: { nodeName?: string } = {}
) {
return useQuery({
queryKey: [...queryKeys.container(environmentId, id), params] as const,
queryFn: () => inspectContainer(environmentId, id, params),
});
}
export async function inspectContainer(
environmentId: EnvironmentId,
id: ContainerId,
{ nodeName }: { nodeName?: string } = {}
) {
try {
const { data } = await axios.get<ContainerJSON>(
urlBuilder(environmentId, id, 'json'),
{ transformResponse: genericHandler, headers: addNodeName(nodeName) }
);
return data;
} catch (e) {
throw parseAxiosError(e, 'Failed starting container');
}
}

@ -0,0 +1,17 @@
import { RawAxiosRequestHeaders } from 'axios';
const AgentTargetHeader = 'X-PortainerAgent-Target';
export function addNodeName(
nodeName?: string,
headers: RawAxiosRequestHeaders = {}
) {
if (!nodeName) {
return headers;
}
return {
...headers,
[AgentTargetHeader]: nodeName,
};
}

@ -6,7 +6,6 @@ import 'angular-messages';
import 'angular-resource';
import 'angular-utils-pagination';
import 'angular-local-storage';
import 'angular-json-tree';
import 'angular-loading-bar';
import 'angular-clipboard';
import 'angular-file-saver';

@ -61,7 +61,6 @@
"angular": "1.8.2",
"angular-clipboard": "^1.6.2",
"angular-file-saver": "^1.1.3",
"angular-json-tree": "1.1.0",
"angular-loading-bar": "~0.9.0",
"angular-local-storage": "~0.5.2",
"angular-messages": "1.8.2",

@ -6915,11 +6915,6 @@ angular-file-saver@^1.1.3:
blob-tmp "^1.0.0"
file-saver "^1.3.3"
angular-json-tree@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/angular-json-tree/-/angular-json-tree-1.1.0.tgz#d7faba97130fc273fa29ef517dbed10342cc645e"
integrity sha512-HVLyrVkEoYVykcIgzMCdhtK2H8Y4jgNujGNqRXNG4x032tp2ZWp34j/hu/E7h6a7X+ODrSTAfRTbkF4f/JX/Fg==
angular-loading-bar@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/angular-loading-bar/-/angular-loading-bar-0.9.0.tgz#37ef52c25f102c216e7b3cdfd2fc5a5df9628e45"

Loading…
Cancel
Save