Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhanced pod logs viewer. Fixes #6199 #11000 #10166 #11030

Merged
merged 25 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this being used?

"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