Skip to content

Commit

Permalink
feat: Enhanced pod logs viewer (#11030)
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Collins <alex_collins@intuit.com>
  • Loading branch information
alexec authored Mar 7, 2023
1 parent 42d1c85 commit 0f500a5
Show file tree
Hide file tree
Showing 23 changed files with 590 additions and 546 deletions.
2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"test": "jest"
},
"dependencies": {
"@types/react-virtualized": "^9.21.21",
"@types/superagent": "^4.1.15",
"ansi-to-react": "^6.1.6",
"argo-ui": "git+https://github.com/argoproj/argo-ui.git",
Expand Down Expand Up @@ -41,6 +42,7 @@
"react-router": "^4.3.1",
"react-router-dom": "^4.2.2",
"react-svg-piechart": "^2.4.2",
"react-virtualized": "^9.22.3",
"redoc": "^2.0.0-rc.64",
"rxjs": "^6.6.6",
"superagent": "^8.0.9",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,17 @@ import * as React from 'react';
import Helmet from 'react-helmet';
import {RouteComponentProps} from 'react-router-dom';
import {Query} from '../../../shared/components';
import {Context} from '../../../shared/context';
import {PodsLogsViewer} from '../pod-logs-viewer/pod-logs-viewer';
import './application-fullscreen-logs.scss';

export const ApplicationFullscreenLogs = (props: RouteComponentProps<{name: string; appnamespace: string; container: string; namespace: string}>) => {
const appContext = React.useContext(Context);
return (
<Query>
{q => {
const podName = q.get('podName');
const name = q.get('name');
const group = q.get('group');
const kind = q.get('kind');
const page = q.get('page');
const untilTimes = (q.get('untilTimes') || '').split(',') || [];
const title = `${podName || `${group}/${kind}/${name}`}:${props.match.params.container}`;
return (
<div className='application-fullscreen-logs'>
Expand All @@ -32,9 +28,6 @@ export const ApplicationFullscreenLogs = (props: RouteComponentProps<{name: stri
kind={kind}
name={name}
podName={podName}
fullscreen={true}
page={{number: parseInt(page, 10) || 0, untilTimes}}
setPage={pageData => appContext.navigation.goto('.', {page: pageData.number, untilTimes: pageData.untilTimes.join(',')}, {replace: true})}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react';
import {Tooltip} from 'argo-ui';

export type ContainerGroup = {offset: number; containers: string[]};

// ContainerSelector is a component that renders a dropdown menu of containers
export const ContainerSelector = ({
containerGroups,
containerName,
onClickContainer
}: {
containerGroups?: ContainerGroup[];
containerName: string;
onClickContainer: (group: ContainerGroup, index: number, logs: string) => void;
}) => {
if (!containerGroups) {
return <></>;
}
const containers = containerGroups?.reduce((acc, group) => acc.concat(group.containers), []);
const containerNames = containers?.map(container => container.name);
const containerGroup = (n: string) => {
return containerGroups.find(group => group.containers.find(container => container === n));
};
const containerIndex = (n: string) => {
return containerGroup(n).containers.findIndex(container => container === n);
};
if (containerNames.length <= 1) return <></>;
return (
<Tooltip content='Select a container to view logs'>
<select className='argo-field' onChange={e => onClickContainer(containerGroup(e.target.value), containerIndex(e.target.value), 'logs')}>
{containerNames.map(n => (
<option key={n} value={n}>
{n}
</option>
))}
</select>
</Tooltip>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import {useContext} from 'react';
import {LogLoader} from './log-loader';
import {Button} from '../../../shared/components/button';
import {Context} from '../../../shared/context';
import {NotificationType} from 'argo-ui/src/components/notifications/notifications';

// CopyLogsButton is a button that copies the logs to the clipboard
export const CopyLogsButton = ({loader}: {loader: LogLoader}) => {
const ctx = useContext(Context);
return (
<Button
title='Copy logs to clipboard'
icon='copy'
onClick={async () => {
try {
await navigator.clipboard.writeText(
loader
.getData()
.map(item => item.content)
.join('\n')
);
ctx.notifications.show({type: NotificationType.Success, content: 'Copied'}, 750);
} catch (err) {
ctx.notifications.show({type: NotificationType.Error, content: err.message});
}
}}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {services, ViewPreferences} from '../../../shared/services';
import * as React from 'react';
import {ToggleButton} from '../../../shared/components/toggle-button';

// DarkModeToggleButton is a component that renders a toggle button that toggles dark mode.
export const DarkModeToggleButton = ({prefs}: {prefs: ViewPreferences}) => (
<ToggleButton
title='Dark Mode'
onToggle={() => {
const inverted = prefs.appDetails.darkMode;
services.viewPreferences.updatePreferences({
...prefs,
appDetails: {...prefs.appDetails, darkMode: !inverted}
});
}}
toggled={prefs.appDetails.darkMode}
icon='moon'
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {services} from '../../../shared/services';
import * as React from 'react';
import {PodLogsProps} from './pod-logs-viewer';
import {Button} from '../../../shared/components/button';

// DownloadLogsButton is a button that downloads the logs to a file
export const DownloadLogsButton = ({applicationName, applicationNamespace, containerName, group, kind, name, namespace, podName}: PodLogsProps) => (
<Button
title='Download logs to file'
icon='download'
onClick={async () => {
const downloadURL = services.applications.getDownloadLogsURL(applicationName, applicationNamespace, namespace, podName, {group, kind, name}, containerName);
window.open(downloadURL, '_blank');
}}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';
import {ToggleButton} from '../../../shared/components/toggle-button';

// FollowToggleButton is a component that renders a button to toggle following logs.
export const FollowToggleButton = ({follow, setFollow}: {follow: boolean; setFollow: (value: boolean) => void}) => (
<ToggleButton title='Follow logs, automatically showing new logs lines' onToggle={() => setFollow(!follow)} toggled={follow} icon='angles-down' />
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Link} from 'react-router-dom';
import * as React from 'react';
import {PodLogsProps} from './pod-logs-viewer';
import {Button} from '../../../shared/components/button';

export const FullscreenButton = ({
applicationName,
applicationNamespace,
containerName,
fullscreen,
group,
kind,
name,
namespace,
podName
}: PodLogsProps & {fullscreen?: boolean}) => {
const fullscreenURL =
`/applications/${applicationNamespace}/${applicationName}/${namespace}/${containerName}/logs?` + `podName=${podName}&group=${group}&kind=${kind}&name=${name}`;
return (
!fullscreen && (
<Link to={fullscreenURL} target='_blank'>
<Button title='Show logs in fullscreen in a new window' icon='external-link-alt' />
</Link>
)
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {DataLoader} from 'argo-ui';
import * as models from '../../../shared/models';

export type LogLoader = DataLoader<models.LogEntry[], string>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react';
import {Tooltip} from 'argo-ui';

// Filter is a component that renders a filter input for log lines.
export const LogMessageFilter = ({filterText, setFilterText}: {filterText: string; setFilterText: (value: string) => void}) => (
<Tooltip content='Filter log lines by text. Prefix with `!` to invert, e.g. `!foo` will find lines without `foo` in them'>
<input className='argo-field' placeholder='containing' value={filterText} onChange={e => setFilterText(e.target.value)} />
</Tooltip>
);
Loading

0 comments on commit 0f500a5

Please sign in to comment.