forked from spinnaker/deck
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(kubernetes): Raw resources UI MVP (spinnaker#8800)
* feat(kubernetes): Raw resources UI MVP * feat(kubernetes): Raw resources UI MVP Address PR feedback from @christopherthielen * feat(kubernetes): Raw resources UI MVP Address final PR feedback from @christopherthielen Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
- Loading branch information
1 parent
a59e0f5
commit c7eb9f4
Showing
23 changed files
with
888 additions
and
309 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
app/scripts/modules/core/src/presentation/icons/vectors/spMenuK8s.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.K8sResources { | ||
width: 100%; | ||
.StandardFieldLayout { | ||
width: 25%; | ||
.StandardFieldLayout_Label { | ||
min-width: 72px; | ||
} | ||
} | ||
} |
108 changes: 108 additions & 0 deletions
108
app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { Application, ApplicationDataSource, FormField, ReactSelectInput } from '@spinnaker/core'; | ||
import React from 'react'; | ||
import { FiltersPubSub } from '../controller/FiltersPubSub'; | ||
import { KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY } from '../rawResource.dataSource'; | ||
import { RawResource } from './group/RawResource'; | ||
import { RawResourceGroups } from './group/RawResourceGroups'; | ||
import { IK8sResourcesFiltersState } from './K8sResourcesFilters'; | ||
import './K8sResources.less'; | ||
import { RawResourceUtils } from './RawResourceUtils'; | ||
|
||
export interface IK8sResourcesProps { | ||
app: Application; | ||
} | ||
|
||
interface IK8sResourcesState { | ||
groupBy: string; | ||
filters: IK8sResourcesFiltersState; | ||
rawResources: IApiKubernetesResource[]; | ||
} | ||
|
||
export class K8sResources extends React.Component<IK8sResourcesProps, IK8sResourcesState> { | ||
private dataSource: ApplicationDataSource<IApiKubernetesResource[]>; | ||
private filterPubSub: FiltersPubSub = FiltersPubSub.getInstance(this.props.app.name); | ||
private sub = this.onFilterChange.bind(this); | ||
|
||
constructor(props: IK8sResourcesProps) { | ||
super(props); | ||
this.dataSource = this.props.app.getDataSource(KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY); | ||
this.state = { | ||
groupBy: 'none', | ||
filters: null, | ||
rawResources: [], | ||
}; | ||
|
||
this.filterPubSub.subscribe(this.sub); | ||
} | ||
|
||
public componentWillUnmount() { | ||
this.filterPubSub.unsubscribe(this.sub); | ||
} | ||
|
||
public onFilterChange(message: IK8sResourcesFiltersState) { | ||
this.setState({ ...this.state, filters: message }); | ||
} | ||
|
||
public async componentDidMount() { | ||
await this.dataSource.ready(); | ||
|
||
this.setState({ | ||
...this.state, | ||
groupBy: this.state.groupBy, | ||
rawResources: await this.dataSource.data.sort((a, b) => a.name.localeCompare(b.name)), | ||
}); | ||
} | ||
|
||
private groupByChanged = (e: React.ChangeEvent<any>): void => { | ||
this.setState({ groupBy: e.target.value.toLowerCase() }); | ||
}; | ||
|
||
public render() { | ||
const opts = ['None', 'Account', 'Kind', 'Namespace']; | ||
return ( | ||
<div className="K8sResources"> | ||
<div className="header row"> | ||
<FormField | ||
onChange={this.groupByChanged} | ||
value={this.state.groupBy.charAt(0).toUpperCase() + this.state.groupBy.substr(1)} | ||
name="groupBy" | ||
label="Group By" | ||
input={(props) => ( | ||
<ReactSelectInput {...props} inputClassName="groupby" stringOptions={opts} clearable={false} /> | ||
)} | ||
/> | ||
</div> | ||
<div className="content"> | ||
{this.state.groupBy === 'none' ? ( | ||
<> | ||
{...this.state.rawResources | ||
.filter((resource) => this.matchFilters(resource)) | ||
.map((resource) => ( | ||
<RawResource key={RawResourceUtils.resourceKey(resource)} resource={resource}></RawResource> | ||
))} | ||
</> | ||
) : ( | ||
<RawResourceGroups | ||
resources={this.state.rawResources.filter((resource) => this.matchFilters(resource))} | ||
groupBy={this.state.groupBy} | ||
></RawResourceGroups> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
private matchFilters(resource: IApiKubernetesResource) { | ||
if (this.state.filters == null) { | ||
return true; | ||
} | ||
const accountMatch = | ||
Object.values(this.state.filters.accounts).every((x) => !x) || this.state.filters.accounts[resource.account]; | ||
const kindMatch = | ||
Object.values(this.state.filters.kinds).every((x) => !x) || this.state.filters.kinds[resource.kind]; | ||
const namespaceMatch = | ||
Object.values(this.state.filters.namespaces).every((x) => !x) || | ||
this.state.filters.namespaces[resource.namespace]; | ||
return accountMatch && kindMatch && namespaceMatch; | ||
} | ||
} |
109 changes: 109 additions & 0 deletions
109
app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { Application, ApplicationDataSource, FilterSection, FilterCheckbox } from '@spinnaker/core'; | ||
import React from 'react'; | ||
import { FiltersPubSub } from '../controller/FiltersPubSub'; | ||
import { RawResourceUtils } from './RawResourceUtils'; | ||
import { KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY } from '../rawResource.dataSource'; | ||
|
||
export interface IK8sResourcesFiltersProps { | ||
app: Application; | ||
} | ||
|
||
export interface IK8sResourcesFiltersState { | ||
accounts: Record<string, boolean>; | ||
kinds: Record<string, boolean>; | ||
namespaces: Record<string, boolean>; | ||
displayNamespaces: Record<string, boolean>; | ||
} | ||
|
||
export class K8sResourcesFilters extends React.Component<IK8sResourcesFiltersProps, IK8sResourcesFiltersState> { | ||
private dataSource: ApplicationDataSource<IApiKubernetesResource[]>; | ||
private filterPubSub: FiltersPubSub = FiltersPubSub.getInstance(this.props.app.name); | ||
|
||
constructor(props: IK8sResourcesFiltersProps) { | ||
super(props); | ||
this.dataSource = this.props.app.getDataSource(KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY); | ||
|
||
this.state = { | ||
accounts: {}, | ||
kinds: {}, | ||
namespaces: {}, | ||
displayNamespaces: {}, | ||
}; | ||
} | ||
|
||
public async componentDidMount() { | ||
await this.dataSource.ready(); | ||
const ns = Object.assign({}, ...this.dataSource.data.map((resource) => ({ [resource.namespace]: false }))); | ||
const displayNs = { ...ns }; | ||
if ('' in displayNs) { | ||
delete displayNs['']; | ||
displayNs[RawResourceUtils.GLOBAL_LABEL] = false; | ||
} | ||
this.setState({ | ||
accounts: Object.assign({}, ...this.dataSource.data.map((resource) => ({ [resource.account]: false }))), | ||
kinds: Object.assign({}, ...this.dataSource.data.map((resource) => ({ [resource.kind]: false }))), | ||
namespaces: ns, | ||
displayNamespaces: displayNs, | ||
}); | ||
} | ||
|
||
public render() { | ||
return ( | ||
<div className="content"> | ||
<FilterSection heading={'Kind'} expanded={true}> | ||
{...Object.keys(this.state.kinds) | ||
.sort((a, b) => a.localeCompare(b)) | ||
.map((key) => ( | ||
<FilterCheckbox | ||
heading={key} | ||
key={key} | ||
sortFilterType={this.state.kinds} | ||
onChange={this.onCheckbox.bind(this)} | ||
></FilterCheckbox> | ||
))} | ||
</FilterSection> | ||
<FilterSection heading={'Account'} expanded={true}> | ||
{...Object.keys(this.state.accounts) | ||
.sort((a, b) => a.localeCompare(b)) | ||
.map((key) => ( | ||
<FilterCheckbox | ||
heading={key} | ||
key={key} | ||
sortFilterType={this.state.accounts} | ||
onChange={this.onCheckbox.bind(this)} | ||
></FilterCheckbox> | ||
))} | ||
</FilterSection> | ||
<FilterSection heading={'Namespace'} expanded={true}> | ||
{...Object.keys(this.state.displayNamespaces) | ||
.sort((a, b) => a.localeCompare(b)) | ||
.map((key) => ( | ||
<FilterCheckbox | ||
heading={key} | ||
key={key} | ||
sortFilterType={this.state.displayNamespaces} | ||
onChange={this.onNsCheckbox.bind(this)} | ||
></FilterCheckbox> | ||
))} | ||
</FilterSection> | ||
</div> | ||
); | ||
} | ||
|
||
private onNsCheckbox() { | ||
const { namespaces, displayNamespaces } = { ...this.state }; | ||
for (const p in displayNamespaces) { | ||
if (p == RawResourceUtils.GLOBAL_LABEL) { | ||
namespaces[''] = displayNamespaces[p]; | ||
} | ||
namespaces[p] = displayNamespaces[p]; | ||
} | ||
this.setState({ namespaces }); | ||
this.filterPubSub.publish(this.state); | ||
} | ||
|
||
private onCheckbox() { | ||
this.setState(this.state); | ||
this.filterPubSub.publish(this.state); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
app/scripts/modules/kubernetes/src/rawResource/component/RawResourceUtils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export class RawResourceUtils { | ||
static GLOBAL_LABEL = '(global)'; | ||
|
||
static namespaceDisplayName(ns: string): string { | ||
if (ns === null || ns === '') { | ||
return RawResourceUtils.GLOBAL_LABEL; | ||
} | ||
return ns; | ||
} | ||
|
||
static resourceKey(resource: IApiKubernetesResource): string { | ||
if (resource === null) { | ||
return ''; | ||
} | ||
return resource.namespace === '' | ||
? resource.account + '-' + resource.kind + '-' + resource.name | ||
: resource.account + '-' + resource.namespace + '-' + resource.kind + '-' + resource.name; | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
app/scripts/modules/kubernetes/src/rawResource/component/group/RawResouceGroup.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React from 'react'; | ||
import './RawResource.less'; | ||
|
||
interface IRawResourceGroupProps { | ||
title: string; | ||
resources?: IApiKubernetesResource[]; | ||
} | ||
|
||
interface IRawResourceGroupState { | ||
open: boolean; | ||
} | ||
|
||
export class RawResourceGroup extends React.Component<IRawResourceGroupProps, IRawResourceGroupState> { | ||
constructor(props: IRawResourceGroupProps) { | ||
super(props); | ||
|
||
this.state = { | ||
open: true, | ||
}; | ||
} | ||
|
||
public render() { | ||
return ( | ||
<div className="RawResourceGroup"> | ||
<div className="clickable sticky-header header" onClick={this.onHeaderClick.bind(this)}> | ||
<span className={`glyphicon pipeline-toggle glyphicon-chevron-${this.state.open ? 'down' : 'right'}`} /> | ||
<div className="shadowed"> | ||
<h4 className="group-title">{this.props.title}</h4> | ||
</div> | ||
</div> | ||
<div className={`items${this.state.open ? '' : ' hidden'}`}>{this.props.children}</div> | ||
</div> | ||
); | ||
} | ||
|
||
private onHeaderClick() { | ||
this.setState({ | ||
open: !this.state.open, | ||
}); | ||
} | ||
} |
Oops, something went wrong.