|
|
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
|
|
import styled from 'styled-components';
|
|
|
import yaml from 'js-yaml';
|
|
|
import close from '../../../../assets/close.png';
|
|
|
+import loading from '../../../../assets/loading.gif';
|
|
|
import _ from 'lodash';
|
|
|
|
|
|
import { ResourceType, ChartType, StorageType, Cluster } from '../../../../shared/types';
|
|
|
@@ -9,6 +10,7 @@ import { Context } from '../../../../shared/Context';
|
|
|
import api from '../../../../shared/api';
|
|
|
|
|
|
import TabRegion from '../../../../components/TabRegion';
|
|
|
+import StatusIndicator from '../../../../components/StatusIndicator';
|
|
|
import RevisionSection from './RevisionSection';
|
|
|
import ValuesYaml from './ValuesYaml';
|
|
|
import GraphSection from './GraphSection';
|
|
|
@@ -29,7 +31,6 @@ type PropsType = {
|
|
|
|
|
|
type StateType = {
|
|
|
loading: boolean,
|
|
|
- error: string | null,
|
|
|
showRevisions: boolean,
|
|
|
components: ResourceType[],
|
|
|
podSelectors: string[]
|
|
|
@@ -40,13 +41,14 @@ type StateType = {
|
|
|
currentTab: string | null,
|
|
|
saveValuesStatus: string | null,
|
|
|
forceRefreshRevisions: boolean, // Update revisions after upgrading values
|
|
|
+ controllers: Record<string, Record<string, any>>,
|
|
|
+ websockets: Record<string, any>,
|
|
|
};
|
|
|
|
|
|
export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
state = {
|
|
|
loading: true,
|
|
|
- error: null as string | null,
|
|
|
- showRevisions: false,
|
|
|
+ showRevisions: true,
|
|
|
components: [] as ResourceType[],
|
|
|
podSelectors: [] as string[],
|
|
|
isPreview: false,
|
|
|
@@ -56,16 +58,18 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
currentTab: null as string | null,
|
|
|
saveValuesStatus: null as (string | null),
|
|
|
forceRefreshRevisions: false,
|
|
|
+ controllers: {} as Record<string, Record<string, any>>,
|
|
|
+ websockets : {} as Record<string, any>,
|
|
|
}
|
|
|
|
|
|
// Retrieve full chart data (includes form and values)
|
|
|
getChartData = (chart: ChartType) => {
|
|
|
let { currentProject } = this.context;
|
|
|
- let { currentCluster, setCurrentChart } = this.props;
|
|
|
- this.setState({ loading: true });
|
|
|
- console.log('tried my best')
|
|
|
+ let { currentCluster, currentChart, setCurrentChart } = this.props;
|
|
|
+
|
|
|
+ this.setState({ loading: true })
|
|
|
api.getChart('<token>', {
|
|
|
- namespace: this.props.namespace,
|
|
|
+ namespace: currentChart.namespace,
|
|
|
cluster_id: currentCluster.id,
|
|
|
storage: StorageType.Secret
|
|
|
}, {
|
|
|
@@ -75,7 +79,6 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
}, (err: any, res: any) => {
|
|
|
if (err) {
|
|
|
console.log('big oof')
|
|
|
- this.setState({ error: 'Could not load chart data.'})
|
|
|
} else {
|
|
|
console.log('did succeed!')
|
|
|
setCurrentChart(res.data);
|
|
|
@@ -87,7 +90,88 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
+
|
|
|
+ getControllers = async (chart: ChartType) => {
|
|
|
+ let { currentCluster, currentProject, setCurrentError } = this.context;
|
|
|
+
|
|
|
+ // don't retrieve controllers for chart that failed to even deploy.
|
|
|
+ if (chart.info.status == 'failed') return;
|
|
|
+
|
|
|
+ await new Promise((next: (res?: any) => void) => {
|
|
|
+ api.getChartControllers('<token>', {
|
|
|
+ namespace: chart.namespace,
|
|
|
+ cluster_id: currentCluster.id,
|
|
|
+ storage: StorageType.Secret
|
|
|
+ }, {
|
|
|
+ id: currentProject.id,
|
|
|
+ name: chart.name,
|
|
|
+ revision: chart.version
|
|
|
+ }, (err: any, res: any) => {
|
|
|
+ if (err) {
|
|
|
+ setCurrentError(JSON.stringify(err));
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ res.data.forEach(async (c: any) => {
|
|
|
+ await new Promise((nextController: (res?: any) => void) => {
|
|
|
+ c.metadata.kind = c.kind
|
|
|
+ this.setState({
|
|
|
+ controllers: {
|
|
|
+ ...this.state.controllers,
|
|
|
+ [c.metadata.uid] : c
|
|
|
+ }
|
|
|
+ }, () => {
|
|
|
+ nextController();
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+ next();
|
|
|
+ });
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
+ setupWebsocket = (kind: string, chart: ChartType) => {
|
|
|
+ let { currentCluster, currentProject } = this.context;
|
|
|
+ let protocol = process.env.NODE_ENV == 'production' ? 'wss' : 'ws';
|
|
|
+ let ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${kind}/status?cluster_id=${currentCluster.id}`);
|
|
|
+ ws.onopen = () => {
|
|
|
+ console.log('connected to websocket');
|
|
|
+ }
|
|
|
+
|
|
|
+ ws.onmessage = (evt: MessageEvent) => {
|
|
|
+ let event = JSON.parse(evt.data);
|
|
|
+ let object = event.Object;
|
|
|
+ object.metadata.kind = event.Kind
|
|
|
+
|
|
|
+ if (!this.state.controllers[object.metadata.uid]) return;
|
|
|
+
|
|
|
+ this.setState({
|
|
|
+ controllers: {
|
|
|
+ ...this.state.controllers,
|
|
|
+ [object.metadata.uid]: object
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ ws.onclose = () => {
|
|
|
+ console.log('closing websocket');
|
|
|
+ }
|
|
|
+
|
|
|
+ ws.onerror = (err: ErrorEvent) => {
|
|
|
+ console.log(err);
|
|
|
+ ws.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ return ws;
|
|
|
+ }
|
|
|
+
|
|
|
+ setControllerWebsockets = (controller_types: any[], chart: ChartType) => {
|
|
|
+ let websockets = controller_types.map((kind: string) => {
|
|
|
+ return this.setupWebsocket(kind, chart);
|
|
|
+ })
|
|
|
+ this.setState({ websockets });
|
|
|
+ }
|
|
|
+
|
|
|
updateResources = () => {
|
|
|
let { currentCluster, currentProject } = this.context;
|
|
|
let { currentChart } = this.props;
|
|
|
@@ -111,18 +195,6 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
|
|
|
refreshChart = () => this.getChartData(this.props.currentChart);
|
|
|
|
|
|
- componentDidMount() {
|
|
|
- this.getChartData(this.props.currentChart);
|
|
|
- }
|
|
|
-
|
|
|
- componentDidUpdate(prevProps: PropsType) {
|
|
|
- /*
|
|
|
- if (this.props.currentChart !== prevProps.currentChart) {
|
|
|
- this.updateResources();
|
|
|
- }
|
|
|
- */
|
|
|
- }
|
|
|
-
|
|
|
onSubmit = (rawValues: any) => {
|
|
|
let { currentProject, currentCluster, setCurrentError } = this.context;
|
|
|
|
|
|
@@ -166,7 +238,6 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
showRevisions,
|
|
|
saveValuesStatus,
|
|
|
tabOptions,
|
|
|
- isPreview,
|
|
|
} = this.state;
|
|
|
let { currentChart, setSidebar, setCurrentView } = this.props;
|
|
|
let chart = currentChart;
|
|
|
@@ -176,10 +247,6 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
return (
|
|
|
<StatusSection currentChart={chart} selectors={podSelectors} />
|
|
|
);
|
|
|
- case 'deploy':
|
|
|
- return (
|
|
|
- <Unimplemented>Coming soon.</Unimplemented>
|
|
|
- );
|
|
|
case 'settings':
|
|
|
return (
|
|
|
<SettingsSection
|
|
|
@@ -269,7 +336,6 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
// Append universal tabs
|
|
|
tabOptions.push(
|
|
|
{ label: 'Status', value: 'status' },
|
|
|
- //{ label: 'Deploy', value: 'deploy' },
|
|
|
{ label: 'Chart Overview', value: 'graph' },
|
|
|
{ label: 'Settings', value: 'settings' },
|
|
|
);
|
|
|
@@ -327,9 +393,61 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
return `${time} on ${date}`;
|
|
|
}
|
|
|
|
|
|
+ getChartStatus = (chartStatus: string) => {
|
|
|
+ if (chartStatus === 'deployed') {
|
|
|
+ for (var uid in this.state.controllers) {
|
|
|
+ let value = this.state.controllers[uid]
|
|
|
+ let status = this.getAvailability(value.metadata.kind, value)
|
|
|
+ if (!status) {
|
|
|
+ return 'loading'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 'deployed'
|
|
|
+ }
|
|
|
+ return chartStatus
|
|
|
+ }
|
|
|
+
|
|
|
+ getAvailability = (kind: string, c: any) => {
|
|
|
+ switch (kind?.toLowerCase()) {
|
|
|
+ case "deployment":
|
|
|
+ case "replicaset":
|
|
|
+ return (c.status.availableReplicas == c.status.replicas)
|
|
|
+ case "statefulset":
|
|
|
+ return (c.status.readyReplicas == c.status.replicas)
|
|
|
+ case "daemonset":
|
|
|
+ return (c.status.numberAvailable == c.status.desiredNumberScheduled)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ componentDidMount() {
|
|
|
+ this.getChartData(this.props.currentChart);
|
|
|
+ this.getControllers(this.props.currentChart)
|
|
|
+ this.setControllerWebsockets(
|
|
|
+ ["deployment", "statefulset", "daemonset", "replicaset"],
|
|
|
+ this.props.currentChart
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ componentDidUpdate(prevProps: PropsType) {
|
|
|
+ /*
|
|
|
+ if (this.props.currentChart !== prevProps.currentChart) {
|
|
|
+ this.updateResources();
|
|
|
+ }
|
|
|
+ */
|
|
|
+ }
|
|
|
+
|
|
|
+ componentWillUnmount() {
|
|
|
+ if (this.state.websockets) {
|
|
|
+ this.state.websockets.forEach((ws: WebSocket) => {
|
|
|
+ ws.close()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
render() {
|
|
|
let { currentChart, setCurrentChart } = this.props;
|
|
|
let chart = currentChart;
|
|
|
+ let status = this.getChartStatus(chart.info.status);
|
|
|
|
|
|
return (
|
|
|
<div>
|
|
|
@@ -342,9 +460,11 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
</Title>
|
|
|
|
|
|
<InfoWrapper>
|
|
|
- <StatusIndicator>
|
|
|
- <StatusColor status={chart.info.status} />{chart.info.status}
|
|
|
- </StatusIndicator>
|
|
|
+ <StatusIndicator
|
|
|
+ controllers={this.state.controllers}
|
|
|
+ status={chart.info.status}
|
|
|
+ margin_left={'0px'}
|
|
|
+ />
|
|
|
<LastDeployed>
|
|
|
<Dot>•</Dot>Last deployed
|
|
|
{' ' + this.readableDate(chart.info.last_deployed)}
|
|
|
@@ -368,6 +488,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
|
|
|
setRevision={this.setRevision}
|
|
|
forceRefreshRevisions={this.state.forceRefreshRevisions}
|
|
|
refreshRevisionsOff={() => this.setState({ forceRefreshRevisions: false })}
|
|
|
+ status={status}
|
|
|
/>
|
|
|
</HeaderWrapper>
|
|
|
|
|
|
@@ -441,15 +562,6 @@ const CloseOverlay = styled.div`
|
|
|
const HeaderWrapper = styled.div`
|
|
|
`;
|
|
|
|
|
|
-const StatusColor = styled.div`
|
|
|
- margin-bottom: 1px;
|
|
|
- width: 8px;
|
|
|
- height: 8px;
|
|
|
- background: ${(props: { status: string }) => (props.status === 'deployed' ? '#4797ff' : props.status === 'failed' ? "#ed5f85" : "#f5cb42")};
|
|
|
- border-radius: 20px;
|
|
|
- margin-right: 16px;
|
|
|
-`;
|
|
|
-
|
|
|
const Dot = styled.div`
|
|
|
margin-right: 9px;
|
|
|
`;
|
|
|
@@ -502,23 +614,6 @@ const NamespaceTag = styled.div`
|
|
|
border-bottom-left-radius: 0px;
|
|
|
`;
|
|
|
|
|
|
-const StatusIndicator = styled.div`
|
|
|
- display: flex;
|
|
|
- height: 20px;
|
|
|
- font-size: 13px;
|
|
|
- flex-direction: row;
|
|
|
- text-transform: capitalize;
|
|
|
- align-items: center;
|
|
|
- font-family: 'Hind Siliguri', sans-serif;
|
|
|
- color: #aaaabb;
|
|
|
- animation: fadeIn 0.5s;
|
|
|
-
|
|
|
- @keyframes fadeIn {
|
|
|
- from { opacity: 0 }
|
|
|
- to { opacity: 1 }
|
|
|
- }
|
|
|
-`;
|
|
|
-
|
|
|
const Icon = styled.img`
|
|
|
width: 100%;
|
|
|
`;
|