Skip to content

Commit

Permalink
feat: Add version information panel (#4312) (#4376)
Browse files Browse the repository at this point in the history
  • Loading branch information
tetchel authored Oct 6, 2020
1 parent 7af7f30 commit 5592150
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 19 deletions.
5 changes: 2 additions & 3 deletions server/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,15 @@ func (s *Server) Version(context.Context, *empty.Empty) (*version.VersionMessage
}
}
if s.kustomizeVersion == "" {
kustomizeVersion, err := kustomize.Version()
kustomizeVersion, err := kustomize.Version(true)
if err == nil {
s.kustomizeVersion = kustomizeVersion
} else {
s.kustomizeVersion = err.Error()
}

}
if s.helmVersion == "" {
helmVersion, err := helm.Version()
helmVersion, err := helm.Version(true)
if err == nil {
s.helmVersion = helmVersion
} else {
Expand Down
22 changes: 14 additions & 8 deletions ui/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import applications from './applications';
import help from './help';
import login from './login';
import settings from './settings';
import {VersionPanel} from './shared/components/version-info/version-info-panel';
import {Provider} from './shared/context';
import {services} from './shared/services';
import requests from './shared/services/requests';
Expand Down Expand Up @@ -52,7 +53,7 @@ const navItems = [
}
];

const versionInfo = services.version.version();
const versionLoader = services.version.version();

async function isExpiredSSO() {
try {
Expand Down Expand Up @@ -92,7 +93,7 @@ requests.onError.subscribe(async err => {
}
});

export class App extends React.Component<{}, {popupProps: PopupProps; error: Error}> {
export class App extends React.Component<{}, {popupProps: PopupProps; showVersionPanel: boolean; error: Error}> {
public static childContextTypes = {
history: PropTypes.object,
apis: PropTypes.object
Expand All @@ -108,7 +109,7 @@ export class App extends React.Component<{}, {popupProps: PopupProps; error: Err

constructor(props: {}) {
super(props);
this.state = {popupProps: null, error: null};
this.state = {popupProps: null, error: null, showVersionPanel: false};
this.popupManager = new PopupManager();
this.notificationsManager = new NotificationsManager();
this.navigationManager = new NavigationManager(history);
Expand Down Expand Up @@ -186,11 +187,15 @@ export class App extends React.Component<{}, {popupProps: PopupProps; error: Err
<Layout
navItems={navItems}
version={() => (
<DataLoader load={() => versionInfo}>
{msg => (
<Tooltip content={msg.Version}>
<span style={{whiteSpace: 'nowrap'}}>{msg.Version}</span>
</Tooltip>
<DataLoader load={() => versionLoader}>
{version => (
<React.Fragment>
<Tooltip content={version.Version}>
<a style={{color: 'white'}} onClick={() => this.setState({showVersionPanel: true})}>
{version.Version}
</a>
</Tooltip>
</React.Fragment>
)}
</DataLoader>
)}>
Expand Down Expand Up @@ -219,6 +224,7 @@ export class App extends React.Component<{}, {popupProps: PopupProps; error: Err
</Provider>
</PageContext.Provider>
<Notifications notifications={this.notificationsManager.notifications} />
<VersionPanel version={versionLoader} isShown={this.state.showVersionPanel} onClose={() => this.setState({showVersionPanel: false})} />
</React.Fragment>
);
}
Expand Down
111 changes: 111 additions & 0 deletions ui/src/app/shared/components/version-info/version-info-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {DataLoader, SlidingPanel, Tooltip} from 'argo-ui';
import * as React from 'react';
import {VersionMessage} from '../../models';

interface VersionPanelProps {
isShown: boolean;
onClose: () => void;
version: Promise<VersionMessage>;
}

type CopyState = 'success' | 'failed' | undefined;

export class VersionPanel extends React.Component<VersionPanelProps, {copyState: CopyState}> {
private readonly header = 'Argo CD Server Version';

constructor(props: VersionPanelProps) {
super(props);
this.state = {copyState: undefined};
}

public render() {
return (
<DataLoader load={() => this.props.version}>
{version => {
return (
<SlidingPanel header={this.header} isShown={this.props.isShown} onClose={() => this.props.onClose()} hasCloseButton={true} isNarrow={true}>
<div className='argo-table-list'>{this.buildVersionTable(version)}</div>
<div>
<Tooltip content='Copy all version info as JSON'>{this.getCopyButton(version)}</Tooltip>
</div>
</SlidingPanel>
);
}}
</DataLoader>
);
}

/**
* Formats the version data and renders the table rows.
*/
private buildVersionTable(version: VersionMessage): JSX.Element {
const formattedVersion = {
'Argo CD': version.Version,
'Build Date': version.BuildDate,
'Go': version.GoVersion,
'Compiler': version.Compiler,
'Platform': version.Platform,
'ksonnet': version.KsonnetVersion,
'kustomize': version.KustomizeVersion,
'Helm': version.HelmVersion,
'kubectl': version.KubectlVersion
};

return (
<React.Fragment>
{Object.entries(formattedVersion).map(([key, value]) => {
return (
<div className='argo-table-list__row' key={key}>
<div className='row'>
<div className='columns small-4' title={key}>
<strong>{key}</strong>
</div>
<div className='columns'>
<Tooltip content={value}>
<span>{value}</span>
</Tooltip>
</div>
</div>
</div>
);
})}
</React.Fragment>
);
}

private getCopyButton(version: VersionMessage): JSX.Element {
let img: string;
let text: string;
if (this.state.copyState === 'success') {
img = 'fa-check';
text = 'Copied';
} else if (this.state.copyState === 'failed') {
img = 'fa-times';
text = 'Copy Failed';
} else {
img = 'fa-copy';
text = 'Copy JSON';
}

return (
<button className='argo-button argo-button--base' style={{marginTop: '1em', minWidth: '18ch'}} onClick={() => this.onCopy(version)}>
<i className={'fa ' + img} />
&nbsp;&nbsp;{text}
</button>
);
}

private async onCopy(version: VersionMessage): Promise<void> {
const stringifiedVersion = JSON.stringify(version, undefined, 4);
try {
await navigator.clipboard.writeText(stringifiedVersion);
this.setState({copyState: 'success'});
} catch (err) {
this.setState({copyState: 'failed'});
}

setTimeout(() => {
this.setState({copyState: undefined});
}, 750);
}
}
9 changes: 9 additions & 0 deletions ui/src/app/shared/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,15 @@ export interface ApplicationSyncWindowState {

export interface VersionMessage {
Version: string;
BuildDate: string;
GoVersion: string;
Compiler: string;
Platform: string;
KsonnetVersion: string;
KustomizeVersion: string;
HelmVersion: string;
KubectlVersion: string;
JsonnetVersion: string;
}

export interface Token {
Expand Down
12 changes: 10 additions & 2 deletions util/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,16 @@ func (h *helm) Dispose() {
h.cmd.Close()
}

func Version() (string, error) {
cmd := exec.Command("helm", "version", "--client")
func Version(shortForm bool) (string, error) {
executable := "helm"
cmdArgs := []string{"version", "--client"}
if shortForm {
cmdArgs = append(cmdArgs, "--short")
}
cmd := exec.Command(executable, cmdArgs...)
// example version output:
// long: "version.BuildInfo{Version:\"v3.3.1\", GitCommit:\"249e5215cde0c3fa72e27eb7a30e8d55c9696144\", GitTreeState:\"clean\", GoVersion:\"go1.14.7\"}"
// short: "v3.3.1+g249e521"
version, err := executil.RunWithRedactor(cmd, redactor)
if err != nil {
return "", fmt.Errorf("could not get helm version: %s", err)
Expand Down
2 changes: 1 addition & 1 deletion util/helm/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func TestHelmArgCleaner(t *testing.T) {
}

func TestVersion(t *testing.T) {
ver, err := Version()
ver, err := Version(false)
assert.NoError(t, err)
assert.NotEmpty(t, ver)
}
Expand Down
30 changes: 26 additions & 4 deletions util/kustomize/kustomize.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,35 @@ func IsKustomization(path string) bool {
return false
}

func Version() (string, error) {
cmd := exec.Command("kustomize", "version")
out, err := executil.Run(cmd)
func Version(shortForm bool) (string, error) {
executable := "kustomize"
cmdArgs := []string{"version"}
if shortForm {
cmdArgs = append(cmdArgs, "--short")
}
cmd := exec.Command(executable, cmdArgs...)
// example version output:
// long: "{Version:kustomize/v3.8.1 GitCommit:0b359d0ef0272e6545eda0e99aacd63aef99c4d0 BuildDate:2020-07-16T00:58:46Z GoOs:linux GoArch:amd64}"
// short: "{kustomize/v3.8.1 2020-07-16T00:58:46Z }"
version, err := executil.Run(cmd)
if err != nil {
return "", fmt.Errorf("could not get kustomize version: %s", err)
}
return strings.TrimSpace(out), nil
version = strings.TrimSpace(version)
if shortForm {
// trim the curly braces
version = strings.TrimPrefix(version, "{")
version = strings.TrimSuffix(version, "}")
version = strings.TrimSpace(version)

// remove double space in middle
version = strings.ReplaceAll(version, " ", " ")

// remove extra 'kustomize/' before version
version = strings.TrimPrefix(version, "kustomize/")

}
return version, nil
}

func getImageParameters(objs []*unstructured.Unstructured) []Image {
Expand Down
2 changes: 1 addition & 1 deletion util/kustomize/kustomize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestParseKustomizeBuildOptions(t *testing.T) {
}

func TestVersion(t *testing.T) {
ver, err := Version()
ver, err := Version(false)
assert.NoError(t, err)
assert.NotEmpty(t, ver)
}

0 comments on commit 5592150

Please sign in to comment.